在網絡編程中,一般都是多線程的編程,這就出現了一個問題:數據的同步與共享。而互斥鎖和條件變量就是為了允許在線程或進程間共享數據、同步的兩種最基本的組成部分。它們總能夠用來同步一個進程中的多個線程。
再進入互斥鎖和條件變量之前,我們先對多線程的一些相關函數進行簡單介紹:
多線程簡單介紹和相關函數:
通常,一個進程中包括多個線程,每個線程都是CPU進行調度的基本單位,多線程可以說是在共享內存空間中並發地多道執行程序,與進程相比,線程的具有以下優點:
● 減少系統調度開銷。由於線程基本不擁有資源,因此線程的切換非常迅速,提高了運行速度,是程序的執行效率更高。
● 線程間通訊方便。由於統一進程的多個線程共享資源,一個線程的數據可以直接為其他線程所用,這使得它們之間的數據交換更加方便快捷。
● 改善程序設計結構。功能復雜的進程可以分為多個獨立的線程分別執行,程序結構清晰,模塊性更強。
線程技術早在20世紀60年代就被提出了,但直到20世紀80年代中期,才把多線程真正應用到操作系統中去。
線程有兩種實現方法:用戶態線程和核心態線程,目前線程主要的實現方法是用戶態線程,用戶態的多線程程序在運行時不需要特定的內核支持,同一個進程的線程之間進行調度切換時,不需要調用系統調用,這使得線程操作的系統開銷減小,但在一個進程中的多個線程的調度中無法發揮多處理的優勢,核心態線程的實現方法允許不同進程中的線程按照相同的調度方法進行調度,有利於發揮多處理器的並發優勢。
2、 Linux多線程操作
編寫Linux下的多線程程序,需要使用頭文件pthread.h,這個頭文件裡定義了線程操作相關的API以及數據結構,編譯鏈接時需要加上編譯器的 –lpthread 選項,指明需要的線程庫文件。
1、線程創建函數
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
參數1(thread):創建線程所用的線程tid號。
參數2(attr): 用來設置新線程的屬性,如果使用默認屬性,可以填寫NULL,還可以使用系統提供的函數,將線程的屬性設置為我們所需要的屬性。線程的屬性主要包括分離屬性,綁定屬性,調度屬性,堆棧屬性,繼承屬性等,屬性結構為pthread_attr_t,在usr/include/pthread.h中定義,屬性值不能直接設置,需要使用相關函數進行操作,初始化的函數為pthread_attr_init()。必須在線程創建函數pthread_create()之前進行。
參數3:該線程鎖鑰執行的操作的入口地址。
參數4:該線程所執行函數的參數,沒有的花為NULL。
2、等待線程結束函數:
int pthread_join(pthread_t thread, void **retval);
用來等待某個指定線程的結束。
參數1:要等待的線程的tid號;
參數2:所等線程存在狀態的返回值,一半設為NULL。
3、線程終止函數
void pthread_exit(void *retval);
在非 隱式調用主線程的結果,任何線程的啟動函數返回pthread_exit(),使用函數的返回值為線程的退出狀態。
允許其他線程繼續執行,主線程通過調用應該終止pthread_exit()
互斥鎖:
互斥鎖只待相互排斥,它是最基本的同步形式。互斥鎖用於保護臨界區(同個進程不同線程均可訪問的區域),以保證任何時刻只有一個線程在執行其中的代碼或者任何時刻只有一個進程在執行其中的代碼。
互斥量從本質上說相當於一把鎖,提供對共享資源的保護訪問,互斥量有兩種狀態,lock和unlock,用來保證一段時間內只有一個線程使用共享資源。
保護一個臨界區大值操作如下:
pthread_mutex_lock(...); //對某個線程上鎖
臨界區;
pthread_mutex_unlock(...); //對相應的線程解鎖
在Linux下,線程的互斥量數據類型是pthread_mutex_t,互斥量操作涉及的函數主要有:
1、初始化互斥鎖
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
參數1:一個metux類型的互斥鎖的地址。
參數2:指定了新建互斥鎖的屬性。如果參數attr為NULL,則使用默認的互斥鎖屬性,默認屬性為快速互斥鎖 。互斥鎖的屬性在創建鎖的時候指定,在LinuxThreads實現中僅有一個鎖類型屬性,不同的鎖類型在試圖對一個已經被鎖定的互斥鎖加鎖時表現不同。
pthread_mutexattr_init()函數成功完成之後會返回零,其他任何返回值都表示出現了錯誤。函數成功執行後,互斥鎖被初始化為鎖住態。
2、線程上鎖函數
int pthread_mutex_lock(pthread_mutex_t *mutex);
3、線程嘗試上鎖函數
int pthread_mutex_trylock(pthread_mutex_t *mutex);
4、線程解鎖函數
int pthread_mutex_unlock(pthread_mutex_t *mutex);
5、線程銷毀互斥鎖函數
int pthread_mutex_destroy(pthread_mutex_t *mutex);
條件變量:
2、 條件變量
只用互斥量很可能會引起死鎖,為此引入了條件變量,條件變量允許線程阻塞和等待另一個線程發送的信號,使用條件變量可以以原子方式阻塞線程,直到滿足某個條件為止,可以避免忙等。
條件變量常和互斥鎖一起使用,互斥量主要用來保證對臨界區的互斥進入,而條件變量則用於線程的阻塞等待,互斥鎖定進入臨界區以後,若條件不滿足,線程便轉為等待狀態,等待條件滿足後被喚醒執行,否則繼續執行,執行完後開鎖。
條件變量類型為pthread_cond_t,相關操作函數為:
1、函數初始化:
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
按參數 attr 指定的屬性創建一個新的條件變量。
參數1:所要創建的條件變量。
參數2 :條件變量的屬性。attr是NULL,則使用默認的屬性創建條件變 量。這是對動態分配的互斥鎖的初始化辦法,對於靜態分配的互斥鎖可以直接賦值初始化:pthread_cond_tcond=PTHREAD_COND_INITIALIZER;
任何一個條件變量在被使用前,都必須初始化,且只能初始化一次(如果是被銷毀了的條件變量,就可以重新初始化)。
2、通知條件變量函數
(1) int pthread_cond_signal(pthread_cond_t *cond);
該函數用來解除一個等待指定事件的線程的阻塞狀態,如果有若干線程掛起等待該條件變量,該調用只喚起一個線程,被喚起的線程並不確定。
參數cond:指定事件的條件量。
(2) int pthread_cond_broadcast(pthread_cond_t *cond);
該函數與用來對所有等待這個條件變量的線程解除阻塞。
參數cond:指定事件的條件變量。
3、等待條件變量函數
(1)int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
該函數的作用是等待一個事件(條件變量)通知,例如上面的例子,在測試a是否為0後,如果不是為0,那麼就調用該函數,將線程掛起,等待對應條件變量事件的通知(被阻塞線程直到有其他線程調用pthread_cond_signal或pthread_cond_broadcast函數置相應的條件變量時才被喚醒)。這些因為等待某個特定條件變量的線程組成一個隊列,這個隊列與這個特定的條件變量直接相關。
參數1(cond):所等待的條件變量。
參數2(mutex):所使用的互斥鎖。
(2)int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
該函數比上一個函數多了一個時間參數,其作用是在一段指定時間內等待一個事件的發生,如果在指定的時間內沒有被喚醒,那麼函數就返回ETIMEDOUT(110)值,表示超時。這個非常有用,可以用來指定線程睡眠時間,值得注意的是,這個時間參數是絕對時間,而不是時間間隔。
參數1(cond):所等待的條件變量。
參數2(mutex):所使用的互斥鎖。
參數3(abstime):指定等待的時間范圍。
一般情況下通知條件變量函數(pyhread_cond_signal())與等待條件變量函數(pthread_cond_wait())配合使用,來達到對共享資源的保護作用。
下面是一個簡單的程序代碼來展現互斥鎖與條件變量的“魔法”:
創建連個線程分別來處理value為奇數和偶數的情況 ,用互斥鎖與條件變量來對共享數據value進行管理。
#include
#include
#include
#include
#include
#define MAX_SIZE 10
int value = 0;
pthread_mutex_t mutex; //定義一個全局的互斥鎖
pthread_cond_t dan, shuang; //定義兩個條件變量來表示單數信號和雙數信號
void * pthread_fun1(void *arg)
{
pthread_mutex_lock(&mutex); //對共享數據value上鎖
while(value < MAX_SIZE){
if(value % 2 == 1){ //value為奇數
printf("fun1 value = %d\n", value);
value++; //value值加1
pthread_cond_signal(&shuang); //發送為雙數信號。通知雙數的處理線程pthread_fun2().
}else{ //value不為奇數
pthread_cond_wait(&dan, &mutex); //阻塞程序,並解開互斥鎖。等待接收到條件變量dan後再進行上鎖並使函數繼續運行。
}
}
pthread_mutex_unlock(&mutex); //對線程解鎖。
}
void* pthread_fun2(void *arg)
{
pthread_mutex_lock(&mutex); //對該線程上鎖,如果該互斥量已經被上鎖則程序阻塞在該處,直到其他線程對其解鎖,它的到資源為止。
while(value < MAX_SIZE){ //如果vallue在預定范圍內
if(value % 2 == 0){ //value是偶數
printf("fun2 value = %d\n", value);
value ++;
pthread_cond_signal(&dan); //發送為雙數信號。通知奇數的處理線程pthread_fun1().
}else{
pthread_cond_wait(&shuang, &mutex); //阻塞程序,並解開互斥鎖。等待接收到條件變量 shuang 後再進行上鎖並使函數繼續運行
}
}
pthread_mutex_unlock(&mutex); //該線程運行結束前,解開互斥鎖。
}
int main()
{
pthread_mutex_init(&mutex,NULL); //互斥鎖初始化
pthread_t tid[2];
pthread_create(&tid[1], NULL, pthread_fun2, &tid[0]); //創建一個線程用來執行pthread_fun2()操作。
sleep(1);
pthread_create(&tid[0], NULL, pthread_fun1, NULL); //創建一個線程,用來執行pthread_fun1()操作。
pthread_join(tid[0], NULL); //等待線程1結束
pthread_join(tid[1], NULL); //等待線程2結束
pthread_mutex_destroy(&mutex); //銷毀互斥鎖。
return 0;
}