【Linux C 多線程編程】互斥鎖與條件變量


一、互斥鎖

 

互斥量從本質上說就是一把鎖, 提供對共享資源的保護訪問。

  1) 初始化:

  在Linux下, 線程的互斥量數據類型是pthread_mutex_t. 在使用前, 要對它進行初始化:

  對於靜態分配的互斥量, 可以把它設置為PTHREAD_MUTEX_INITIALIZER, 或者調用pthread_mutex_init.

  對於動態分配的互斥量, 在申請內存(malloc)之后, 通過pthread_mutex_init進行初始化, 並且在釋放內存(free)前需要調用pthread_mutex_destroy.

  原型:

  int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restric attr);

  int pthread_mutex_destroy(pthread_mutex_t *mutex);

  頭文件:#include<pthread.h>

  返回值: 成功則返回0, 出錯則返回錯誤編號.

  說明: 如果使用默認的屬性初始化互斥量, 只需把attr設為NULL. 其他值在以后講解。

  2) 互斥操作:

  對共享資源的訪問, 要對互斥量進行加鎖, 如果互斥量已經上了鎖, 調用線程會阻塞, 直到互斥量被解鎖. 在完成了對共享資源的訪問后, 要對互斥量進行解鎖。

  首先說一下加鎖函數:

  頭文件:#include<pthread.h>

  原型:

  int pthread_mutex_lock(pthread_mutex_t *mutex);

  int pthread_mutex_trylock(pthread_mutex_t *mutex);

  返回值: 成功則返回0, 出錯則返回錯誤編號.

  說明: 具體說一下trylock函數, 這個函數是非阻塞調用模式, 也就是說, 如果互斥量沒被鎖住, trylock函數將把互斥量加鎖, 並獲得對共享資源的訪問權限; 如果互斥量被鎖住了, trylock函數將不會阻塞等待而直接返回EBUSY, 表示共享資源處於忙狀態。

  再說一下解所函數:

  頭文件:

  原型: int pthread_mutex_unlock(pthread_mutex_t *mutex);

  返回值: 成功則返回0, 出錯則返回錯誤編號.

  3) 死鎖:

  死鎖主要發生在有多個依賴鎖存在時, 會在一個線程試圖以與另一個線程相反順序鎖住互斥量時發生. 如何避免死鎖是使用互斥量應該格外注意的東西。

  總體來講, 有幾個不成文的基本原則:

  對共享資源操作前一定要獲得鎖。

  完成操作以后一定要釋放鎖。

  盡量短時間地占用鎖。

  如果有多鎖, 如獲得順序是ABC連環扣, 釋放順序也應該是ABC。

  線程錯誤返回時應該釋放它所獲得的鎖。

  互斥鎖最簡單的使用是這樣的:

1 pthread_mutex_t mutex;                   //定義鎖
2 pthread_mutex_init(&mutex, NULL);   //默認屬性初始化鎖
3 pthread_mutex_lock(&mutex);           //申請鎖
4 ...
5 pthread_mutex_unlock(&mutex);       //釋放鎖

  pthread_mutex_lock阻塞線程直到pthread_mutex_unlock被調用。
  還有另外兩個函數可以用:int pthread_mutex_trylock (pthread_mutex_t *__mutex), 該函數立即返回,根據返回狀態判斷加鎖是否成功。int pthread_mutex_timedlock (pthread_mutex_t *mutex, struct timespec *__restrict),該函數超時返回。
  互斥鎖主要用來互斥訪問臨界區。用於線程的互斥。

 

設置鎖的屬性
函數pthread_mutexattr_setpshared和函數pthread_mutexattr_settype用來設置互斥鎖屬性。
前一個函數設置屬性pshared,它有兩個取值,PTHREAD_PROCESS_PRIVATE和PTHREAD_PROCESS_SHARED。前者用來不同進程中的線程同步,后者用於同步本進程的不同線程。在上面的例子中,我們使用的是默認屬性PTHREAD_PROCESS_ PRIVATE。后者用來設置互斥鎖類型,可選的類型有PTHREAD_MUTEX_NORMAL、PTHREAD_MUTEX_ERRORCHECK、PTHREAD_MUTEX_RECURSIVE和PTHREAD _MUTEX_DEFAULT。它們分別定義了不同的上所、解鎖機制,一般情況下,選用最后一個默認屬性。用法如下:

pthread_mutexattr_t mutexAttr;
pthread_mutexattr_init(&mutexAttr);
pthread_mutexattr_setpshared(&mutexAttr, PTHREAD_PROCESS_PRIVATE);
pthread_mutexattr_settype(&mutexAttr, PTHREAD_MUTEX_DEFAULT);
pthread_mutex_init(&mutex, &mutexAttr);

pthread_mutexattr_setpshared設置互斥鎖范圍

用法

1 int    pthread_mutexattr_setpshared(pthread_mutexattr_t* mattr,int pshared);

pthread_mutexattr_setpshared() 成功完成之后會返回零。其他任何返回值都表示出現了錯誤。如果出現以下情況,該函數將失敗並返回對應的值。

pthread_mutexattr_getpshared(&mattr, &pshared)獲取互斥鎖范圍

1 int    pthread_mutexattr_getpshared(pthread_mutexattr_t *mattr,int *pshared)

此函數可為屬性對象 mattr 獲取 pshared 的當前值。該值為 PTHREAD_PROCESS_SHARED 或 PTHREAD_PROCESS_PRIVATE

  PTHREAD_PROCESS_PRIVATE

  如果互斥鎖的 pshared 屬性設置為 PTHREAD_PROCESS_PRIVATE,則僅有那些由同一個進程創建的線程才能夠處理該互斥鎖。

  PTHREAD_PROCESS_SHARED

  互斥鎖變量可以是進程專用的(進程內)變量,也可以是系統范圍內的(進程間)變量。要在多個進程中的線程之間共享互斥鎖,可以在共享內存中創建互斥鎖,並將 pshared 屬性設置為 PTHREAD_PROCESS_SHARED

 

pthread_mutexattr_settype設置互斥鎖類型的屬性

用法

1 int pthread_mutexattr_settype(pthread_mutexattr_t  *attr , int type);

類型屬性的缺省值為 PTHREAD_MUTEX_DEFAULT

如果運行成功,pthread_mutexattr_settype 函數會返回零。否則,將返回用於指明錯誤的錯誤號。

 pthread_mutexattr_gettype獲取互斥鎖類型的屬性

用法

1 int pthread_mutexattr_gettype(pthread_mutexattr_t  *attr , int  *type);

pthread_mutexattr_getpshared() 成功完成之后會返回零。其他任何返回值都表示出現了錯誤。如果出現以下情況,該函數將失敗並返回對應的值。

  PTHREAD_MUTEX_NORMAL

  此類型的互斥鎖不會檢測死鎖。如果線程在不首先解除互斥鎖的情況下嘗試重新鎖定該互斥鎖,則會產生死鎖。嘗試解除由其他線程鎖定的互斥鎖會產生不確定的行為。如果嘗試解除鎖定的互斥鎖未鎖定,則會產生不確定的行為。

  PTHREAD_MUTEX_ERRORCHECK

  此類型的互斥鎖可提供錯誤檢查。如果線程在不首先解除鎖定互斥鎖的情況下嘗試重新鎖定該互斥鎖,則會返回錯誤。如果線程嘗試解除鎖定的互斥鎖已經由其他線程鎖定,則會返回錯誤。如果線程嘗試解除鎖定的互斥鎖未鎖定,則會返回錯誤。

  PTHREAD_MUTEX_RECURSIVE

  如果線程在不首先解除鎖定互斥鎖的情況下嘗試重新鎖定該互斥鎖,則可成功鎖定該互斥鎖。 與 PTHREAD_MUTEX_NORMAL 類型的互斥鎖不同,對此類型互斥鎖進行重新鎖定時不會產生死鎖情況。多次鎖定互斥鎖需要進行相同次數的解除鎖定才可以釋放該鎖,然后其他線程才能獲取該互斥鎖。如果線程嘗試解除鎖定的互斥鎖已經由其他線程鎖定,則會返回錯誤。 如果線程嘗試解除鎖定的互斥鎖未鎖定,則會返回錯誤。

  PTHREAD_MUTEX_DEFAULT

  如果嘗試以遞歸方式鎖定此類型的互斥鎖,則會產生不確定的行為。對於不是由調用線程鎖定的此類型互斥鎖,如果嘗試對它解除鎖定,則會產生不確定的行為。對於尚未鎖定的此類型互斥鎖,如果嘗試對它解除鎖定,也會產生不確定的行為。允許在實現中將該互斥鎖映射到其他互斥鎖類型之一。對於 Solaris 線程,PTHREAD_PROCESS_DEFAULT 會映射到 PTHREAD_PROCESS_NORMAL

 

 pthread_mutexattr_setprotocol設置互斥鎖屬性的協議

1 int pthread_mutexattr_setprotocol(pthread_mutexattr_t *attr, int protocol);

如果成功完成,pthread_mutexattr_setprotocol() 會返回 0。其他任何返回值都表示出現了錯誤。

pthread_mutexattr_getprotocol設置互斥鎖屬性的協議

1 int pthread_mutexattr_getprotocol(const pthread_mutexattr_t *attr, int *protocol);

如果成功完成,pthread_mutexattr_getprotocol() 會返回 0。其他任何返回值都表示出現了錯誤。

  • PTHREAD_PRIO_NONE

    線程的優先級和調度不會受到互斥鎖擁有權的影響。

  • PTHREAD_PRIO_INHERIT

    此協議值(如 thrd1)會影響線程的優先級和調度。如果更高優先級的線程因 thrd1 所擁有的一個或多個互斥鎖而被阻塞,而這些互斥鎖是用 PTHREAD_PRIO_INHERIT 初始化的,則 thrd1 將以高於它的優先級或者所有正在等待這些互斥鎖(這些互斥鎖是 thrd1 指所擁有的互斥鎖)的線程的最高優先級運行。

    如果 thrd1 因另一個線程 (thrd3) 擁有的互斥鎖而被阻塞,則相同的優先級繼承效應會以遞歸方式傳播給 thrd3

    使用 PTHREAD_PRIO_INHERIT 可以避免優先級倒置。低優先級的線程持有較高優先級線程所需的鎖時,便會發生優先級倒置。只有在較低優先級的線程釋放該鎖之后,較高優先級的線程才能繼續使用該鎖。設置 PTHREAD_PRIO_INHERIT,以便按與預期的優先級相反的優先級處理每個線程。

    如果為使用協議屬性值 PTHREAD_PRIO_INHERIT 初始化的互斥鎖定義了 _POSIX_THREAD_PRIO_INHERIT,則互斥鎖的屬主失敗時會執行以下操作。屬主失敗時的行為取決於 pthread_mutexattr_setrobust_np() 的 robustness 參數的值。

    • 解除鎖定互斥鎖。

    • 互斥鎖的下一個屬主將獲取該互斥鎖,並返回錯誤 EOWNERDEAD

    • 互斥鎖的下一個屬主會嘗試使該互斥鎖所保護的狀態一致。如果上一個屬主失敗,則狀態可能會不一致。如果屬主成功使狀態保持一致,則可針對該互斥鎖調用 pthread_mutex_init() 並解除鎖定該互斥鎖。


      注 –

      如果針對以前初始化的但尚未銷毀的互斥鎖調用 pthread_mutex_init(),則該互斥鎖不會重新初始化。


    • 如果屬主無法使狀態保持一致,請勿調用 pthread_mutex_init(),而是解除鎖定該互斥鎖。在這種情況下,所有等待的線程都將被喚醒。以后對 pthread_mutex_lock() 的所有調用將無法獲取互斥鎖,並將返回錯誤代碼 ENOTRECOVERABLE。現在,通過調用pthread_mutex_destroy() 來取消初始化該互斥鎖,即可使其狀態保持一致。調用 pthread_mutex_init() 可重新初始化互斥鎖。

    • 如果已獲取該鎖的線程失敗並返回 EOWNERDEAD,則下一個屬主將獲取該鎖及錯誤代碼 EOWNERDEAD

  • PTHREAD_PRIO_PROTECT

    當線程擁有一個或多個使用 PTHREAD_PRIO_PROTECT 初始化的互斥鎖時,此協議值會影響其他線程(如 thrd2)的優先級和調度。thrd2 以其較高的優先級或者以 thrd2 擁有的所有互斥鎖的最高優先級上限運行。基於被 thrd2 擁有的任一互斥鎖阻塞的較高優先級線程對於 thrd2 的調度沒有任何影響。

  如果某個線程調用 sched_setparam() 來更改初始優先級,則調度程序不會采用新優先級將該線程移到調度隊列末尾。

  • 線程擁有使用 PTHREAD_PRIO_INHERIT 或 PTHREAD_PRIO_PROTECT 初始化的互斥鎖

  • 線程解除鎖定使用 PTHREAD_PRIO_INHERIT 或 PTHREAD_PRIO_PROTECT 初始化的互斥鎖

  一個線程可以同時擁有多個混合使用 PTHREAD_PRIO_INHERIT 和 PTHREAD_PRIO_PROTECT 初始化的互斥鎖。在這種情況下,該線程將以通過其中任一協議獲取的最高優先級執行。

 

pthread_mutexattr_setprioceiling設置互斥鎖的優先級上限

1 int pthread_mutexattr_setprioceiling(pthread_mutexatt_t *attr,int prioceiling, int oldceiling);

attr 指示以前調用 pthread_mutexattr_init() 時創建的互斥鎖屬性對象。

prioceiling 指定已初始化互斥鎖的優先級上限。優先級上限定義執行互斥鎖保護的臨界段時的最低優先級。prioceiling 位於 SCHED_FIFO 所定義的優先級的最大范圍內。要避免優先級倒置,請將 prioceiling 設置為高於或等於可能會鎖定特定互斥鎖的所有線程的最高優先級。

oldceiling 包含以前的優先級上限值。

pthread_mutexattr_getprioceiling獲取互斥鎖的優先級上限

1 int pthread_mutex_getprioceiling(const pthread_mutex_t *mutex,int *prioceiling);

注 –

僅當定義了 _POSIX_THREAD_PRIO_PROTECT 符號時,attr 互斥鎖屬性對象才會包括優先級上限屬性。

 

pthread_mutexattr_setrobust_np設置互斥鎖的強健屬性

1 int pthread_mutexattr_setrobust_np(pthread_mutexattr_t *attr,int robustness);

注 –

僅當定義了符號 _POSIX_THREAD_PRIO_INHERIT 時,pthread_mutexattr_setrobust_np() 才適用。

attr 指示以前通過調用 pthread_mutexattr_init() 創建的互斥鎖屬性對象。

robustness 定義在互斥鎖的屬主失敗時的行為。pthread.h 中定義的 robustness 的值為 PTHREAD_MUTEX_ROBUST_NP 或PTHREAD_MUTEX_STALLED_NP。缺省值為 PTHREAD_MUTEX_STALLED_NP

  • PTHREAD_MUTEX_ROBUST_NP

    如果互斥鎖的屬主失敗,則以后對 pthread_mutex_lock() 的所有調用將以不確定的方式被阻塞。

  • PTHREAD_MUTEX_STALLED_NP

    互斥鎖的屬主失敗時,將會解除鎖定該互斥鎖。互斥鎖的下一個屬主將獲取該互斥鎖,並返回錯誤 EOWNWERDEAD


    注 –

    應用程序必須檢查 pthread_mutex_lock() 的返回代碼,查找返回錯誤 EOWNWERDEAD 的互斥鎖。


    • 互斥鎖的新屬主應使該互斥鎖所保護的狀態保持一致。如果上一個屬主失敗,則互斥鎖狀態可能會不一致。

    • 如果新屬主能夠使狀態保持一致,請針對該互斥鎖調用 pthread_mutex_consistent_np(),並解除鎖定該互斥鎖。

    • 如果新屬主無法使狀態保持一致,請勿針對該互斥鎖調用 pthread_mutex_consistent_np(),而是解除鎖定該互斥鎖。

      所有等待的線程都將被喚醒,以后對 pthread_mutex_lock() 的所有調用都將無法獲取該互斥鎖。返回代碼為 ENOTRECOVERABLE。通過調用 pthread_mutex_destroy() 取消對互斥鎖的初始化,並調用 pthread_mutex_int() 重新初始化該互斥鎖,可使該互斥鎖保持一致。

    如果已獲取該鎖的線程失敗並返回 EOWNERDEAD,則下一個屬主獲取該鎖時將返回代碼 EOWNERDEAD

pthread_mutexattr_getrobust_np設置互斥鎖的強健屬性

1 int pthread_mutexattr_getrobust_np(const pthread_mutexattr_t *attr, int *robustness);

注 –

僅當定義了符號 _POSIX_THREAD_PRIO_INHERIT 時,pthread_mutexattr_getrobust_np() 才適用。


attr 指示以前通過調用 pthread_mutexattr_init() 創建的互斥鎖屬性對象。

robustness 是互斥鎖屬性對象的強健屬性值。

 

  下面給個測試小程序進一步了解互斥,mutex互斥信號量鎖住的不是一個變量,而是阻塞住一段程序。如果對一個mutex變量testlock, 執行了第一次pthread_mutex_lock(testlock)之后,在unlock(testlock)之前的這段時間內,如果有其他線程也執行到了pthread_mutex_lock(testlock),這個線程就會阻塞住,直到之前的線程unlock之后才能執行,由此,實現同步,也就達到保護臨界區資源的目的。

 1 #include<stdio.h>
 2 #include<pthread.h>
 3 
 4 static pthread_mutex_t testlock;
 5 pthread_t test_thread;
 6 
 7 void *test()
 8 {
 9 pthread_mutex_lock(&testlock);
10 printf("thread Test() \n");
11 pthread_mutex_unlock(&testlock);
12 }
13 
14 int main()
15 {
16 pthread_mutex_init(&testlock, NULL);
17 pthread_mutex_lock(&testlock); 
18 
19 printf("Main lock \n");
20 
21 pthread_create(&test_thread, NULL, test, NULL);
22 sleep(1); //更加明顯的觀察到是否執行了創建線程的互斥鎖
23 printf("Main unlock \n");
24 pthread_mutex_unlock(&testlock); 
25 
26 sleep(1);
27 pthread_join(test_thread,NULL); 
28 pthread_mutex_destroy(&testlock); 
29 return 0;
30 }
31 
32 make
33 gcc -D_REENTRANT -lpthread -o test test.c
34 
35 結果:
36 Main lock 
37 Main unlock 
38 thread Test()

 

二、條件變量

條件變量用來阻塞線程等待某個事件的發生,並且當等待的事件發生時,阻塞線程會被通知。

互斥鎖一個明顯的缺點是它只有兩種狀態:鎖定和非鎖定。而條件變量通過允許線程阻塞和等待另一個線程發送信號的方法彌補了互斥鎖的不足,它常和互斥鎖一起使用。

條件變量是利用線程間共享的全局變量進行同步的一種機制,主要包括兩個動作:一個線程等待"條件變量的條件成立"而掛起;另一個線程使"條件成立"(給出條件成立信號)。為了防止競爭,條件變量的使用總是和一個互斥鎖結合在一起。 

條件變量的結構為pthread_cond_t

最簡單的使用例子:

1 pthread_cond_t cond;
2 pthread_cond_init(&cond, NULL);
3 pthread_cond_wait(&cond, &mutex);
4     ...
5 pthread_cond_signal(&cond);

1)創建和注銷   

條件變量和互斥鎖一樣,都有靜態動態兩種創建方式,靜態方式使用PTHREAD_COND_INITIALIZER常量,如下:     
pthread_cond_t   cond=PTHREAD_COND_INITIALIZER     

動態方式調用pthread_cond_init()函數,API定義如下:     

1 int   pthread_cond_init(pthread_cond_t   *cond,pthread_condattr_t   *cond_attr)

盡管POSIX標准中為條件變量定義了屬性,但在LinuxThreads中沒有實現,因此cond_attr值通常為NULL,且被忽略。   
注銷一個條件變量需要調用pthread_cond_destroy(),只有在沒有線程在該條件變量上等待的時候才能注銷這個條件變量,否則返回EBUSY。因為Linux實現的條件變量沒有分配什么資源,所以注銷動作只包括檢查是否有等待線程。API定義如下:     

1 int   pthread_cond_destroy(pthread_cond_t   *cond)   

  

2)等待和激發

等待:

1 int   pthread_cond_wait(pthread_cond_t   *cond,   pthread_mutex_t   *mutex)   
2 int   pthread_cond_timedwait(pthread_cond_t   *cond,   pthread_mutex_t   *mutex,   const   struct   timespec   *abstime)     

等待條件有兩種方式:無條件等待pthread_cond_wait()和計時等待pthread_cond_timedwait(),其中計時等待方式如果在給定時刻前條件沒有滿足,則返回ETIMEOUT,結束等待,其中abstime以與time()系統調用相同意義的絕對時間形式出現,0表示格林尼治時間1970年1月1日0時0分0秒。 且進入wait時會把傳入的互斥鎖解鎖。

 無論哪種等待方式,都必須和一個互斥鎖配合,以防止多個線程同時請求pthread_cond_wait()(或pthread_cond_timedwait(),下同)的競爭條件(Race   Condition)。

mutex互斥鎖必須是普通鎖(PTHREAD_MUTEX_TIMED_NP)或者適應鎖(PTHREAD_MUTEX_ADAPTIVE_NP),且在調用pthread_cond_wait()前必須由本線程加鎖(pthread_mutex_lock()),而在更新條件等待隊列以前,mutex保持鎖定狀態,並在線程掛起進入等待前解鎖。在條件滿足從而離開pthread_cond_wait()之前,mutex將被重新加鎖,以與進入pthread_cond_wait()前的加鎖動作對應。   執行pthread_cond_wait()時自動解鎖互斥量(如同執行了 pthread_unlock_mutex),並等待條件變量觸發。這時線程掛起,不占用 CPU 時間,直到條件變量被觸發。

因此,全過程可以描述為:

(1)pthread_mutex_lock()上鎖,

(2)pthread_cond_wait()等待,等待過程分解為為:解鎖--條件滿足--加鎖

(3)pthread_mutex_unlock()解鎖。 

 

激發:

1 /* 喚醒一個等待該條件變量cond的線程 */
2 int pthread_cond_signal (pthread_cond_t *cond);
3 /* 喚醒所有等待條件變量cond的線程 */
4 int pthread_cond_broadcast (pthread_cond_t *cond);

激發條件有兩種形式,pthread_cond_signal()激活一個等待該條件的線程,存在多個等待線程時按入隊順序激活其中一個;而pthread_cond_broadcast()則激活所有等待線程。 兩者 如果沒有等待的線程,則什么也不做。

 

屬性設置:

/* 初始化條件變量屬性對象  */
int pthread_condattr_init (pthread_condattr_t * attr);
/* 銷毀條件變量屬性對象  */
int pthread_condattr_destroy (pthread_condattr_t * attr);
/* 獲取條件變量屬性對象在進程間共享與否的標識  */
int pthread_condattr_getpshared (const pthread_condattr_t* attr,int*  pshared);
/* 設置條件變量屬性對象,標識在進程間共享與否 */
int pthread_condattr_setpshared (pthread_condattr_t* attr, int  pshared) ;

  PTHREAD_PROCESS_PRIVATE

  如果條件變量的 pshared 屬性設置為 PTHREAD_PROCESS_PRIVATE,則僅有那些由同一個進程創建的線程才能夠處理該條件變量。

  PTHREAD_PROCESS_SHARED

  條件變量變量可以是進程專用的(進程內)變量,也可以是系統范圍內的(進程間)變量。要在多個進程中的線程之間共享條件變量,可以在共享內存中創建條件變量,並將 pshared 屬性設置為 PTHREAD_PROCESS_SHARED

 

實例:

 1 #include <pthread.h>  
 2     using namespace std;  
 3   
 4     pthread_cond_t qready = PTHREAD_COND_INITIALIZER;    //初始構造條件變量  
 5     pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER;    //初始構造鎖  
 6     pthread_t tid1,tid2,tid3;  
 7   
 8     int x = 10;  
 9     int y = 20;  
10   
11   
12     void *thrd_1(void *arg)  
13     {  
14         pthread_mutex_lock(&qlock);  
15         while(x<y)  
16         {  
17             pthread_cond_wait(&qready,&qlock);  
18         }  
19         pthread_mutex_unlock(&qlock);  
20         cout<<"1"<<endl;  
21         sleep(5);  
22     }  
23   
24     void *thrd_2(void *arg)  
25     {  
26         pthread_mutex_lock(&qlock);  
27         x = 20;  
28         y = 10;  
29         cout<<"has change x and y"<<endl;  
30   
31         pthread_mutex_unlock(&qlock);  
32         if(x > y)  
33         {  
34             pthread_cond_signal(&qready);  
35         }  
36         cout<<"2"<<endl;  
37     }  
38   
39     void *thrd_3(void *arg)  
40     {  
41         pthread_join(tid1,NULL);  
42         cout<<"3"<<endl;  
43     }  
44   
45     int main(int argc,char **argv)  
46     {  
47         int err;  
48         err = pthread_create(&tid1,NULL,thrd_1,NULL);  
49         if(err != 0)  
50         {  
51             cout<<"pthread 1 create error"<<endl;  
52         }  
53         err = pthread_create(&tid2,NULL,thrd_2,NULL);  
54         if(err != 0)  
55         {  
56             cout<<"pthread 2 create error"<<endl;  
57         }  
58         err = pthread_create(&tid3,NULL,thrd_3,NULL);  
59         if(err != 0)  
60         {  
61             cout<<"pthread 3 create error"<<endl;  
62         }  
63         while(1)  
64         {  
65             sleep(1);  
66         }  
67         return 0;  
68           
69     }

  可以看到,創建了3個線程后,執行順序2,1,3,即打印出的數字是213。為什么是這個順序呢?我們接下去看,當創建tid1線程的時候,進入線程函數,並且加上了鎖,然后進入pthread_cond_wait函數,這個函數的功能是等待qready這個條件變量成功,這個條件是什么呢?我們稍后在看,現在我們只要知道,這個函數在qready條件沒滿足的時候會卡在這里,並且,會把傳入的互斥鎖解鎖,為什么要解鎖的,擬可以想想,如果不解鎖的話,那外部就沒有可以對x,y的修改權,應為其他兩個線程想要修改這兩個值的話都需要對qclock進行枷鎖。

  好了線程1就這樣,那之后就會運行線程2,我們看線程2的線程函數,該函數一開始也加了鎖,但當線程1的pthread_cond_wait解鎖之后,他就可以繼續運行了,並且,在之后,它對x,y進行了修改,改好之后進行了解鎖,並且調用了pthread_cond_signal通知線程1,現在可以知道了吧。這個滿足的條件就是要x>y。
   現在這里有個問題,一定要在發送通知之前解鎖嗎?答案是肯定的,為什么,因為如果先發送通知信號給線程1的時候,pthread_cond_wait可能在線程2的解鎖之前就返回,而當它返回的時候,會再次將這個所進行鎖定,而這個所還沒有在線程2中解鎖,應次會使其在次卡住。雖然這個卡住在線程2運行到解鎖處會消除,但這並不符合我們有時的需求,所以最好還是在解鎖之后在發送信號。(如果看不懂的話,可以參考下面紅色字體的部分!!!)
    所以可以看出為什么線程2總是在線程1之前執行完畢,線程3就更不用說了,pthread_join你們懂的!!!
 
三、信號量

信號量本質上是一個非負的整數計數器,它被用來控制對公共資源的訪問。當公共資源增加時,調用函數sem_post()增加信號量。
只有當信號量值大於0時,才能使用公共資源,使用后,函數sem_wait()減少信號量。函數sem_trywait()和函數
pthread_ mutex_trylock()起同樣的作用,它是函數sem_wait()的非阻塞版本。

下面我們逐個介紹和信號量有關的一些函數,它們都在頭文件/usr/include/semaphore.h中定義。
  信號量的數據類型為結構sem_t,它本質上是一個長整型的數。函數sem_init()用來初始化一個信號量。它的原型為:
  extern int sem_init __P ((sem_t *__sem, int __pshared, unsigned int __value));
  sem為指向信號量結構的一個指針;pshared不為0時此信號量在進程間共享,否則只能為當前進程的所有線程共享;value給出了信號量的初始值。
  函數sem_post( sem_t *sem )用來增加信號量的值。當有線程阻塞在這個信號量上時,調用這個函數會使其中的一個線程不在阻塞,選擇機制同樣是由線程的調度策略決定的。
  函數sem_wait( sem_t *sem )被用來阻塞當前線程直到信號量sem的值大於0,解除阻塞后將sem的值減一,表明公共資源經使用后減少。函數sem_trywait ( sem_t *sem )是函數sem_wait()的非阻塞版本,它直接將信號量sem的值減一。
  函數sem_destroy(sem_t *sem)用來釋放信號量sem。

在上面的生產者、消費者例子中,我們假設緩沖區最多能放10條消息。用信號量來實現如下:

復制代碼
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>

static char buff[50];
pthread_mutex_t mutex;
pthread_mutex_t cond_mutex;
pthread_cond_t cond;
sem_t msg_cnt;      //緩沖區消息數
sem_t space_cnt;   //緩沖區空閑數

void consumeItem(char *buff)
{
    printf("consumer item\n");
}
void produceItem(char *buff)
{
    printf("produce item\n");
}
void *consumer(void *param)
{
    while (1)
    {
        sem_wait(&msg_cnt);
        pthread_mutex_lock(&mutex);
        consumeItem(buff);
        pthread_mutex_unlock(&mutex);
        sem_post(&space_cnt);
    }
    return NULL;
}

void *producer(void *param)
{
    while (1)
    {
        sem_wait(&space_cnt);
        pthread_mutex_lock(&mutex);
        produceItem(buff);        
        pthread_mutex_unlock(&mutex);
        sem_post(&msg_cnt);
        
    }
    return NULL;
}

int main()
{
    pthread_t tid_c, tid_p;
    void *retval;
    pthread_mutex_init(&mutex, NULL);
    pthread_mutex_init(&cond_mutex, NULL);
    
    pthread_cond_t cond;
    pthread_cond_init(&cond, NULL);

    sem_init(&msg_cnt, 0, 0);        //初始緩沖區沒有消息
    sem_init(&space_cnt, 0, 10);  //初始緩沖區能放10條消息
    
    pthread_create(&tid_p, NULL, producer, NULL);
    pthread_create(&tid_c, NULL, consumer, NULL);
    
        
    pthread_join(tid_p, &retval);
    pthread_join(tid_c, &retval);
            
    return 0;
}
復制代碼

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM