Linux系統下的執行緒控制
想要了解執行緒的控制,首先我們要理解執行緒的概念。
執行緒的概念:
程序是作業系統中資源管理的最小單位。執行緒是程式執行的最小單位。 在作業系統設計上,從程序演化出執行緒最主要的目的就是更好地支援多處理器以及減少上下文切換開銷。 一個程序至少需要一個執行緒作為它的指令執行體,程序管理著計算機資源,而將執行緒分配到某個CPU上執行。 對作業系統來說,程序佔有系統資源,程序的切換也給作業系統帶來了額外的開銷。每次建立新程序會把父程序的資源複製一份到子程序,如果建立多個程序的話,會佔用大量的資源。 程序間的資料共享也需要作業系統的干預。 執行緒是一種輕量級的程序。 執行緒沒有系統資源。(1).資源分配不同
程序擁有獨立的記憶體和系統資源,而在一個程序內部,執行緒之間的資源是共享的,系統不會為執行緒分配系統資源。
程序擁有系統資源,在系統切換的時候,作業系統要保留程序佔用的資源;執行緒的切換不需要保留系統資源,切換效率遠高於程序。
(3).執行方式不同執行緒有程式執行的入口地址,但是執行緒不能獨立執行。由於執行緒不佔有系統資源,所以執行緒必須放在程序中。程序可以被作業系統直接排程。一個程序內部的多個執行緒可共享資源和排程,不同程序之間的執行緒資源不能直接共享。
系統呼叫執行緒控制的過程: (1).執行緒建立 格式:#include <pthread.h>
int pthread_create(pthread_t
#include <pthread.h>
pthread_tpthread_self(void);
說明:pthread_tpthread_self()返回呼叫的執行緒的識別符號。每個執行緒都有自己的執行緒識別符號,以便在程序內區分,執行緒識別符號在pthread_create建立時產生。 (3).執行緒等待 格式:#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
說明:pthread_join()將呼叫它的執行緒阻塞,一直等到被等待的執行緒結束為止,當函式返回時,被等待執行緒的資源被收回。第一個引數thread為被等待的執行緒識別符號,第二個引數retval為使用者定義的指標,存放被等待執行緒的返回值。 (4).執行緒退出 格式:#include <pthread.h>
void pthread_exit(void *retval);
int pthread_cancel(pthread_t thread);
說明:pthread_exit()終止呼叫執行緒,retval為執行緒的返回值;pthread_cancel終止由引數thread指定的執行緒。 程式要求: 編寫程式建立一個執行緒,該執行緒顯示3次字串“This is apthread”,父程序顯示3次字串“This is the main process”。 程式例項:#include <stdio.h>
#include <pthread.h>
void thread(void)
{
int i;
for (i=0;i<3;i++)
printf("This is a pthread.\n");
}
int main(void)
{
pthread_t id;
int i,ret;
ret=pthread_create(&id,NULL,(void *) thread,NULL);
if (ret!=0)
{
printf ("Create pthread error!\n");
exit (1);
}
for (i=0;i<3;i++)
printf("This is the main process.\n");
pthread_join(id,NULL);
return(0);
}
編譯並執行,因執行緒相關函式是執行在使用者空間的執行緒庫pthread.h實現,所以編譯的時候要加上-lpthread選項。
執行緒同步: 多執行緒程式設計因為無法知道哪個執行緒會在哪個時候對共享資源進行操作,因此讓如何保護共享資源變得複雜,通過使用執行緒間的同步,可以解決執行緒間對資源的競爭。 包括兩種基本方法,第一種是“訊號量”,第二種是“互斥量”。選擇哪種方法取決於程式的實際需要。例如控制共享記憶體,使之在任何一個時刻只有一個執行緒能夠對它進行訪問,使用互斥量更為合適。但如果需要控制一組同等物件的訪問權,例如從5條電話線裡給某個執行緒分配一條,計數訊號量就更合適。 1.訊號量同步 訊號量相關的函式名字以“sem”作為字首,執行緒裡使用的基本訊號量函式有4個,被包含在標頭檔案semaphore.h中。初始化訊號量可使用函式sem_init(),它的一般形式是:intsem_init(sem_t*sem,intpshared, unsignedint value);
其中,第一個引數是sem_t結構的指標,該結構用於儲存訊號量的資訊。第二個引數控制訊號量的型別,如果引數值為0,表示該訊號量是區域性的,否則其它程式就能共享這個訊號量。第三個引數是訊號量的初始值。
2.互斥量同步互斥量的作用猶如給某個物件加上一把鎖,每次只允許一個執行緒去訪問它。如果想對程式碼關鍵部分的訪問進行控制,可以在進入這段程式碼之前鎖定一個互斥量,完成操作之後再解開它。 使用互斥量要用到的基本函式與訊號量需要使用的函式很相識,同樣是4個,它們的一般形式如下:
int pthread_mutex_init(pthread_mutex_t*mutex, const pthread_mutexattr_t*mutexattr);
int pthread_mutex_lock(pthread_mutex_t*mutex));
int pthread_mutex_unlock(pthread_mutex_t*mutex);
int pthread_mutex_destroy(pthread_mutex_t*mutex);
pthread_mutex_init( )函式用於建立一個互斥量,第一個引數是指向互斥量的資料結構pthread_mutex_t的指標,第二個引數是定義互斥量屬性的pthread_mutexattrt結構的指標,它的預設型別是fast。類似於訊號量的使用方法。
pthread_mutex_lock( )是對互斥量進行鎖定操作,pthread_mutex_unlock()是對互斥量進行解鎖操作。函式pthread_mutex_destroy()的作用是清除互斥量。
對一個已經加了鎖的互斥量呼叫pthread_mutex_lock()函式,那麼程式本身就會被阻塞;而因為擁有互斥量的那個執行緒現在也是被阻塞的執行緒之一,所以互斥量就永遠也打不開了,程式將進入死鎖狀態。 要避免死鎖有兩種做法:一是讓它檢測有可能發生死鎖的這種現象並返回一個錯誤;二是讓它遞迴地操作,允許同一個執行緒加上好幾把鎖,但前提是以後必須有同等數量的解鎖鑰匙。程式如下: 3.兩種方法對比Mutex是一把鑰匙,一個人拿了就可進入一個房間,出來的時候把鑰匙交給佇列的第一個。 Semaphore是一件可以容納N人的房間,如果人不滿就可以進去,如果人滿了,就要等待有人出來。對於N=1的情況,稱為binary semaphore。 Binary semaphore與Mutex的差異:
1. mutex要由獲得鎖的執行緒來釋放(誰獲得,誰釋放)。而semaphore可以由其它執行緒釋放。
2.初始狀態可能不一樣:mutex的初始值是1 ,而semaphore的初始值可能是0(或者為1)。
取消執行緒: 有時需要讓一個執行緒能請求另外一個執行緒結束,可使用pthread_cancel()函式傳送一個要求取消執行緒的訊號。該函式的一般形式是:intpthread_cancel(pthread_tthread);
引數中指定的執行緒在收到取消請求後,會對自己稍做一些處理,然後結束。
線上程函式中可使用pthread_setcancelstate()設定自己的取消狀態,該函式的一般形式是:
intpthread_setcancelstate(int state,int*oldstate); 第一個引數是狀態的設定值,它可以是一個列舉常量,定義有:PTHREAD_CANCEL_ENABLE,這個值允許執行緒接收取消請求;PTHREAD_CANCEL_DISABLE,這個值遮蔽取消請求。第二個引數是執行緒的取消狀態,該狀態的定義與建立執行緒的函式相同,如果沒有特殊要求可傳送NULL。 如果取消請求被接受了,執行緒會進入第二個控制層次,用pthread_setcanceltype()函式設定取消型別。函式的一般形式是:intpthread_setcanceltype(int type,int*oldtype);
type引數可以有兩種取值,一個是PTHREAD_CANCEL_ASYNCHRONOUS,表示執行緒接受取消請求後立即採取行動;另一個是PTHREAD_CANCEL_DEFERRED,表示在接收到取消請求之後、採取實際行動之前,先執行pthread_join()、pthread_cond_wait()、pthread_cond_tomewait()、pthread_testcancel()、sem_wait()或sigwait()函式。程式實現如下:#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h> //包含執行緒庫
#include<string.h>
void *thread_function(void *arg); //定義執行緒函式原型
char message[]= "THREAD_TEST"; //定義公用的記憶體空間
int main()
{
int res; //用於儲存執行緒的返回值
pthread_t a_thread; //用於儲存執行緒的識別符號等資訊
void *thread_result; //用於接受執行緒結束時的返回值
res=pthread_create(&a_thread,NULL,thread_function,(void*)message); //建立執行緒
if(res!=0) //判斷執行緒是否有錯誤
{
perror("執行緒建立失敗");
exit(EXIT_FAILURE);
}
printf("等待執行緒結束...\n");
res=pthread_join(a_thread, &thread_result); //等待執行緒結束
if(res!=0) //判斷執行緒是否有錯誤
{
perror("等待執行緒結束");
exit(EXIT_FAILURE);
}
printf("執行緒已結束,返回值:%s\n",(char *)thread_result); //輸出執行緒返回的訊息
printf("Message的值為:%s\n",message); //輸出公用的記憶體空間的值
exit(EXIT_SUCCESS);
}
void *thread_function(void *arg) //定義執行緒函式的細節
{
printf("執行緒在執行,引數為:%s\n",(char *)arg); //輸出執行緒的引數
sleep(3); //使執行緒休眠3秒
strcpy(message,"執行緒修改"); //修改公用的記憶體空間的值
pthread_exit("執行緒執行完畢"); //結束執行緒
}
程式中,原有執行緒在睡眠3秒後,發出一個結束新執行緒的請求。新執行緒的取消狀態被設定為允許取消,取消的型別為延遲取消。當新執行緒收到取消請求後,至少執行了pthread_join()函式。這樣原有執行緒裡就能收到新執行緒已經取消的訊息了。 多執行緒的實現: 程式執行時建立的執行緒可以被當作主執行緒,主執行緒可以創建出多個執行緒,而新建立的執行緒裡也能再建立執行緒。程式如下:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
#define NUM_THREADS 6 //定義執行緒的總數
void *thread_function(void *arg);
int main()
{
int res;
pthread_t a_thread[NUM_THREADS]; //定義執行緒陣列
void *thread_result;
int lots_of_threads;
for(lots_of_threads=0;lots_of_threads<NUM_THREADS;lots_of_threads++)
{
res=pthread_create(&(a_thread[lots_of_threads]),NULL,thread_function,(void *) &lots_of_threads); //建立一個執行緒
if(res!=0)
{
perror("執行緒建立失敗");
exit(EXIT_FAILURE);
}
sleep(1); //主執行緒休眠1秒
}
printf("等待執行緒結束...\n");
for(lots_of_threads=NUM_THREADS-1;lots_of_threads>=0;lots_of_threads--)
{
res=pthread_join(a_thread[lots_of_threads],&thread_result); //等待執行緒結束
if(res==0)
{
printf("結束一個執行緒\n");
}
else
{
perror("執行緒結束失敗");
}
}
printf("執行緒結束失敗\n");
exit(EXIT_SUCCESS);
}
void *thread_function(void *arg) //定義執行緒函式
{
int my_number=*(int *)arg; //接受主執行緒傳遞的引數,該引數可以是任意型別
int rand_num;
printf("執行緒函式已執行,引數為:%d\n",my_number);
rand_num=1+(int)(9.0*rand()/(RAND_MAX+1.0)); //獲得一個隨機數
sleep(rand_num); //執行緒以隨機數定義的時間休眠
printf("第%d個執行緒結束\n",my_number); //結束執行緒
pthread_exit(NULL);
}