Linux多線程與同步機制


作者:Vamei 出處:http://www.cnblogs.com/vamei 歡迎轉載,也請保留這段聲明。謝謝!

 

典型的UNIX系統都支持一個進程創建多個線程(thread)。在Linux進程基礎中提到,Linux以進程為單位組織操作,Linux中的線程也都基於進程。盡管實現方式有異於其它的UNIX系統,但Linux的多線程在邏輯和使用上與真正的多線程並沒有差別。

 

一. 多線程

我們先來看一下什么是多線程。在Linux從程序到進程中,我們看到了一個程序在內存中的表示。這個程序的整個運行過程中,只有一個控制權的存在。當函數被調用的時候,該函數獲得控制權,成為激活(active)函數,然后運行該函數中的指令。與此同時,其它的函數處於離場狀態,並不運行。如下圖所示:

Linux從程序到進程

 

我們看到,各個方塊之間由箭頭連接。各個函數就像是連在一根線上一樣,計算機像一條流水線一樣執行各個函數中定義的操作。這樣的一個程序叫做單線程程序。


多線程就是允許一個進程內存在多個控制權,以便讓多個函數同時處於激活狀態,從而讓多個函數的操作同時運行。即使是單CPU的計算機,也可以通過不停地在不同線程的指令間切換,從而造成多線程同時運行的效果。如下圖所示,就是一個多線程的流程:

main()到func3()再到main()構成一個線程,此外func1()和func2()構成另外兩個線程。操作系統一般都有一些系統調用來讓你將一個函數運行成為一個新的線程。

 

回憶我們在Linux從程序到進程中提到的stack的功能和用途。由於stack中只有最下方的stack frame可以被讀取,所以我們只能有該frame對應的單一函數處於激活狀態。為了實現多線程,我們必須繞開stack帶給我們的限制。為此,當我們創建一個新的線程的時候,我們為這個線程創建一個新的stack。當該stack執行到全部彈出的時候,該線程完成任務並結束。所以,多線程的進程在內存中會有多個stack,相互之間以一定的空白區域隔開,以備stack的增長。每個線程隨時可以使用自己stack中最下方的stack frame中的參數和變量,並與其它線程共享內存中的Text,heap和global data區域。在上面的例子中,我們將在進程空間中有三個stack。

(要注意的是,對於多線程來說,由於同一個進程空間中存在多個stack,任何一個空白區域被填滿都會導致stack overflow的問題。)

 

2. 並發

多線程相當於一個並發(concunrrency)系統。並發系統一般同時執行多個任務。如果多個任務可以共享資源,特別是同時寫入某個變量的時候,就需要解決同步的問題。比如說,我們有一個多線程火車售票系統,用全局變量i存儲剩余的票數。多個線程不斷地賣票(i = i - 1),直到剩余票數為0。所以每個都需要執行如下操作:

復制代碼
/*mu is a global mutex*/

while
(1) {    /*infinite loop*/ if (i != 0) i = i -1 else { printf("no more tickets"); exit(); } }
復制代碼

如果只有一個線程執行上面的程序的時候(相當於一個窗口售票),則沒有問題。但如果多個線程都執行上面的程序(相當於多個窗口售票), 我們就會出現問題。我們會看到,其根本原因在於同時發生的各個線程都可以對i讀取和寫入。

我們這里的if結構會給CPU兩個指令, 一個是判斷是否有剩余的票(i != 0), 一個是賣票 (i = i -1)。某個線程會先判斷是否有票(比如說此時i為1),但兩個指令之間存在一個時間窗口,其它線程可能在此時間窗口內執行賣票操作(i = i -1),導致該線程賣票的條件不再成立。但該線程由於已經執行過了判斷指令,所以無從知道i發生了變化,所以繼續執行賣票指令,以至於賣出不存在的票 (i成為負數)。對於一個真實的售票系統來說,這將成為一個嚴重的錯誤 (售出了過多的票,火車爆滿)。

在並發情況下,指令執行的先后順序由內核決定。同一個線程內部,指令按照先后順序執行,但不同線程之間的指令很難說清除哪一個會先執行。如果運行的結果依賴於不同線程執行的先后的話,那么就會造成競爭條件(race condition),在這樣的狀況下,計算機的結果很難預知。我們應該盡量避免競爭條件的形成。最常見的解決競爭條件的方法是將原先分離的兩個指令構成不可分隔的一個原子操作(atomic operation),而其它任務不能插入到原子操作中。

 

3. 多線程同步(synchronization)

對於多線程程序來說,同步是指在一定的時間內只允許某一個線程訪問某個資源 。而在此時間內,不允許其它的線程訪問該資源。我們可以通過互斥鎖(mutex)條件變量(condition variable)讀寫鎖(reader-writer lock)來同步資源。

 

1) mutex

 

  盡管在Posix Thread中同樣可以使用IPC的信號量機制來實現互斥鎖mutex功能,但顯然semphore的功能過於強大了,在Posix Thread中定義了另外一套專門用於線程同步的mutex函數。

 

1. 創建和銷毀

 

   有兩種方法創建互斥鎖,靜態方式和動態方式。

 

   1).POSIX定義了一個宏PTHREAD_MUTEX_INITIALIZER來靜態初始化互斥鎖,方法如下:

 

  pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;

 

在LinuxThreads實現中,pthread_mutex_t是一個結構,而PTHREAD_MUTEX_INITIALIZER則是一個結構常量。

 

   2).動態方式是采用pthread_mutex_init()函數來初始化互斥鎖,API定義如下:

  int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);

 

其中mutexattr用於指定互斥鎖屬性(見下),如果為NULL則使用缺省屬性。

 

   pthread_mutex_destroy()用於注銷一個互斥鎖,API定義如下:

  int pthread_mutex_destroy(pthread_mutex_t *mutex);

 

銷毀一個互斥鎖即意味着釋放它所占用的資源,且要求鎖當前處於開放狀態。由於在Linux中,互斥鎖並不占用任何資源,因此LinuxThreads中的pthread_mutex_destroy()除了檢查鎖狀態以外(鎖定狀態則返回EBUSY)沒有其他動作。

 

2. 互斥鎖屬性

 

   互斥鎖的屬性在創建鎖的時候指定,在LinuxThreads實現中僅有一個鎖類型屬性,不同的鎖類型在試圖對一個已經被鎖定的互斥鎖加鎖時表現不同。當前(glibc2.2.3,linuxthreads0.9)有四個值可供選擇:

 

   PTHREAD_MUTEX_TIMED_NP,這是缺省值,也就是普通鎖。當一個線程加鎖以后,其余請求鎖的線程將形成一個等待隊列,並在解鎖后按優先級獲得鎖。這種鎖策略保證了資源分配的公平性。
PTHREAD_MUTEX_RECURSIVE_NP,嵌套鎖,允許同一個線程對同一個鎖成功獲得多次,並通過多次unlock解鎖。如果是不同線程請求,則在加鎖線程解鎖時重新競爭。
   PTHREAD_MUTEX_ERRORCHECK_NP,檢錯鎖,如果同一個線程請求同一個鎖,則返回EDEADLK,否則與PTHREAD_MUTEX_TIMED_NP類型動作相同。這樣就保證當不允許多次加鎖時不會出現最簡單情況下的死鎖。
PTHREAD_MUTEX_ADAPTIVE_NP,適應鎖,動作最簡單的鎖類型,僅等待解鎖后重新競爭。

 


3. 鎖操作

 

   鎖操作主要包括加鎖pthread_mutex_lock()、解鎖pthread_mutex_unlock()和測試加鎖pthread_mutex_trylock()三個,不論哪種類型的鎖,都不可能被兩個不同的線程同時得到,而必須等待解鎖。對於普通鎖和適應鎖類型,解鎖者可以是同進程內任何線程;而檢錯鎖則必須由加鎖者解鎖才有效,否則返回EPERM;對於嵌套鎖,文檔和實現要求必須由加鎖者解鎖,但實驗結果表明並沒有這種限制,這個不同目前還沒有得到解釋。在同一進程中的線程,如果加鎖后沒有解鎖,則任何其他線程都無法再獲得鎖。

 

  1 int pthread_mutex_lock(pthread_mutex_t *mutex)
  2 int pthread_mutex_unlock(pthread_mutex_t *mutex)
  3 int pthread_mutex_trylock(pthread_mutex_t *mutex)

 

 

pthread_mutex_trylock()語義與pthread_mutex_lock()類似,不同的是在鎖已經被占據時返回EBUSY而不是掛起等待。

 

4. 其他

 

   POSIX線程鎖機制的Linux實現都不是取消點,因此,延遲取消類型的線程不會因收到取消信號而離開加鎖等待。值得注意的是,如果線程在加鎖后解鎖前被取消,鎖將永遠保持鎖定狀態,因此如果在關鍵區段內有取消點存在,或者設置了異步取消類型,則必須在退出回調函數中解鎖。

 

   這個鎖機制同時也不是異步信號安全的,也就是說,不應該在信號處理過程中使用互斥鎖,否則容易造成死鎖。

 

-----

mutex是一個特殊的變量,它有鎖上(lock)和打開(unlock)兩個狀態。mutex一般被設置成全局變量。打開的mutex可以由某個線程獲得。一旦獲得,這個mutex會鎖上,此后只有該線程有權打開。其它想要獲得mutex的線程,會等待直到mutex再次打開的時候。我們可以將mutex想像成為一個只能容納一個人的洗手間,當某個人進入洗手間的時候,可以從里面將洗手間鎖上。其它人只能在mutex外面等待那個人出來,才能進去。在外面等候的人並沒有排隊,誰先看到洗手間空了,就可以首先沖進去。

上面的問題很容易使用mutex的問題解決,每個線程的程序可以改為:

復制代碼
/*mu is a global mutex*/

while (1) { /*infinite loop*/ mutex_lock(mu);       /*aquire mutex and lock it, if cannot, wait until mutex is unblocked*/ if (i != 0) i = i - 1; else { printf("no more tickets"); exit(); } mutex_unlock(mu);     /*release mutex, make it unblocked*/ }
復制代碼

第一個執行mutex_lock()的線程會先獲得mu。其它想要獲得mu的線程必須等待,直到第一個線程執行到mutex_unlock()釋放mu,才可以獲得mu,並繼續執行線程。所以線程在mutex_lock()和mutex_unlock()之間的操作時,不會被其它線程影響,就構成了一個原子操作

需要注意的時候,如果存在某個線程依然使用原先的程序 (即不嘗試獲得mu,而直接修改i),mutex不能阻止該程序修改i,mutex就失去了保護資源的意義。所以,mutex機制需要程序員自己來寫出完善的程序來實現mutex的功能。我們下面講的其它機制也是如此。

 ----

互斥鎖用來保證一段時間內只有一個線程在執行一段代碼。必要性顯而易見:假設各個線程向同一個文件順序寫入數據,最后得到的結果一定是災難性的。
  先看下面一段代碼。這是一個讀/寫程序,它們公用一個緩沖區,並且假定一個緩沖區只能保存一條信息。即緩沖區只有兩個狀態:有信息或沒有信息。

 1 void reader_function ( void );
 2 void writer_function ( void );
 3 
 4 char buffer;
 5 int buffer_has_item=0;
 6 pthread_mutex_t mutex;
 7 struct timespec delay;
 8 
 9 void main ( void ){
10     pthread_t reader;
11     /* 定義延遲時間*/
12     delay.tv_sec = 2;
13     delay.tv_nec = 0;
14     /* 用默認屬性初始化一個互斥鎖對象*/
15     pthread_mutex_init (&mutex,NULL);
16     pthread_create(&reader, pthread_attr_default, (void          *)&reader_function), NULL);
17     writer_function( );
18 }
19 
20 void writer_function (void){
21     while(1){
22         /* 鎖定互斥鎖*/
23         pthread_mutex_lock (&mutex);
24         if (buffer_has_item==0){
25             buffer=make_new_item( );
26             buffer_has_item=1;
27         }
28         /* 打開互斥鎖*/
29         pthread_mutex_unlock(&mutex);
30         pthread_delay_np(&delay);
31     }
32 }
33 
34 void reader_function(void){
35     while(1){
36         pthread_mutex_lock(&mutex);
37         if(buffer_has_item==1){
38             consume_item(buffer);
39             buffer_has_item=0;
40         }
41         pthread_mutex_unlock(&mutex);
42         pthread_delay_np(&delay);
43     }
44 }                

 

  這里聲明了互斥鎖變量mutex,結構pthread_mutex_t為不公開的數據類型,其中包含一個系統分配的屬性對象。函數pthread_mutex_init用來生成一個互斥鎖。NULL參數表明使用默認屬性。如果需要聲明特定屬性的互斥鎖,須調用函數pthread_mutexattr_init函數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_mutex_lock聲明開始用互斥鎖上鎖,此后的代碼直至調用pthread_mutex_unlock為止,均被上鎖,即同一時間只能被一個線程調用執行。當一個線程執行到pthread_mutex_lock處時,如果該鎖此時被另一個線程使用,那此線程被阻塞,即程序將等待到另一個線程釋放此互斥鎖。在上面的例子中,使用了pthread_delay_np函數,讓線程睡眠一段時間,就是為了防止一個線程始終占據此函數。

  在使用互斥鎖的過程中很有可能會出現死鎖:兩個線程試圖同時占用兩個資源,並按不同的次序鎖定相應的互斥鎖,例如兩個線程都需要鎖定互斥鎖1和互斥鎖2,a線程先鎖定互斥鎖1,b線程先鎖定互斥鎖2,這時就出現了死鎖。此時可以使用函數pthread_mutex_trylock,它是函數pthread_mutex_lock的非阻塞版本,當它發現死鎖不可避免時,它會返回相應的信息,程序員可以針對死鎖做出相應的處理。另外不同的互斥鎖類型對死鎖的處理不一樣,但最主要的還是要程序員自己在程序設計注意這一點。

 

2) condition variable

 

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

 

1. 創建和注銷

 

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

    pthread_cond_t cond=PTHREAD_COND_INITIALIZER

 

 

 

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

    int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr)

 

 

 

盡管POSIX標准中為條件變量定義了屬性,但在LinuxThreads中沒有實現,因此cond_attr值通常為NULL,且被忽略。

 

   注銷一個條件變量需要調用pthread_cond_destroy(),只有在沒有線程在該條件變量上等待的時候才能注銷這個條件變量,否則返回EBUSY。因為Linux實現的條件變量沒有分配什么資源,所以注銷動作只包括檢查是否有等待線程。API定義如下:
int pthread_cond_destroy(pthread_cond_t *cond)

 

2. 等待和激發

 

    int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
    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秒。

 

   無論哪種等待方式,都必須和一個互斥鎖配合,以防止多個線程同時請求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_signal()激活一個等待該條件的線程,存在多個等待線程時按入隊順序激活其中一個;而pthread_cond_broadcast()則激活所有等待線程。

 

3. 其他

 

pthread_cond_wait()和pthread_cond_timedwait()都被實現為取消點,因此,在該處等待的線程將立即重新運行,在重新鎖定mutex后離開pthread_cond_wait(),然后執行取消動作。也就是說如果pthread_cond_wait()被取消,mutex是保持鎖定狀態的,因而需要定義退出回調函數來為其解鎖。

 

以下示例集中演示了互斥鎖和條件變量的結合使用,以及取消對於條件等待動作的影響。在例子中,有兩個線程被啟動,並等待同一個條件變量,如果不使用退出回調函數(見范例中的注釋部分),則tid2將在pthread_mutex_lock()處永久等待。如果使用回調函數,則tid2的條件等待及主線程的條件激發都能正常工作。

 

 1 #include <stdio.h>
 2 #include <pthread.h>
 3 #include <unistd.h>
 4 
 5 pthread_mutex_t mutex;
 6 pthread_cond_t  cond;
 7 
 8 void * child1(void *arg)
 9 {
10         pthread_cleanup_push(pthread_mutex_unlock,&mutex);  /* comment 1 */
11         while(1){
12           printf("thread 1 get running \n");
13           printf("thread 1 pthread_mutex_lock returns %d\n",
14        pthread_mutex_lock(&mutex));
15           pthread_cond_wait(&cond,&mutex);
16        printf("thread 1 condition applied\n");
17           pthread_mutex_unlock(&mutex);
18           sleep(5);
19       }
20         pthread_cleanup_pop(0);     /* comment 2 */
21 }
22 void *child2(void *arg)
23 {
24         while(1){
25       sleep(3);               /* comment 3 */
26          printf("thread 2 get running.\n");
27           printf("thread 2 pthread_mutex_lock returns %d\n",
28        pthread_mutex_lock(&mutex));
29           pthread_cond_wait(&cond,&mutex);
30           printf("thread 2 condition applied\n");
31           pthread_mutex_unlock(&mutex);
32           sleep(1);
33         }
34 }
35 int main(void)
36 {
37         int tid1,tid2;
38         printf("hello, condition variable test\n");
39         pthread_mutex_init(&mutex,NULL);
40         pthread_cond_init(&cond,NULL);
41         pthread_create(&tid1,NULL,child1,NULL);
42         pthread_create(&tid2,NULL,child2,NULL);
43         do{
44             sleep(2);                   /* comment 4 */
45             pthread_cancel(tid1);       /* comment 5 */
46             sleep(2);                   /* comment 6 */
47             pthread_cond_signal(&cond);
48         }while(1);  
49         sleep(100);
50         pthread_exit(0);
51 }

 

 

 

 

   如果不做注釋5的pthread_cancel()動作,即使沒有那些sleep()延時操作,child1和child2都能正常工作。注釋3和注釋4的延遲使得child1有時間完成取消動作,從而使child2能在child1退出之后進入請求鎖操作。如果沒有注釋1和注釋2的回調函數定義,系統將掛起在child2請求鎖的地方;而如果同時也不做注釋3和注釋4的延時,child2能在child1完成取消動作以前得到控制,從而順利執行申請鎖的操作,但卻可能掛起在pthread_cond_wait()中,因為其中也有申請mutex的操作。child1函數給出的是標准的條件變量的使用方式:回調函數保護,等待條件前鎖定,pthread_cond_wait()返回后解鎖。

 

條件變量機制不是異步信號安全的,也就是說,在信號處理函數中調用pthread_cond_signal()或者pthread_cond_broadcast()很可能引起死鎖。

 

-------

condition variable是另一種常用的變量。它也常常被保存為全局變量,並和mutex合作。

 

假設我們有這樣一個狀況: 我們有100個工人,每人負責裝修一個房間。當有10個房間裝修完成的時候,我們就通知相應的十個工人一起去喝啤酒。我們如何實現呢?我們可以讓工人在裝修好房間之后,去檢查已經裝修好的房間數。但多線程條件下,會有競爭條件的危險(其他工人會在該工人裝修好房子和檢查之間完成工作)。

復制代碼
/*mu: global mutex, cond: global codition variable, num: global int*/
mutex_lock(mu) num
= num + 1; /*worker build the room*/ if (num <= 10) { /*worker is within the first 10 to finish*/ cond_wait(mu, cond);     /*wait*/ printf("drink beer"); } else if (num = 11) { /*workder is the 11th to finish*/ cond_broadcast(mu, cond);        /*inform the other 9 to wake up*/ } mutex_unlock(mu);
復制代碼

通常, condition variable除了要和mutex配合之外,還需要和另一個全局變量配合(這里的num, 也就是裝修好的房間數)。這個全局變量用來構成各個條件。

我們讓工人在裝修好房間(num = num + 1)之后,去檢查已經裝修好的房間數( num < 10 )。由於mu被鎖上,所以不會有其他工人在此期間裝修房間(改變num的值)。如果該工人是前十個完成的人,那么我們就調用cond_wait()函數。
cond_wait()做兩件事情,一個是釋放mu,從而讓別的工人可以建房。另一個是等待,直到cond的通知。這樣的話,符合條件的線程就開始等待。當有通知(第十個房間已經修建好)到達的時候,condwait()會再次鎖上mu,並恢復線程的運行,我們會執行下一句prinft("drink beer") (我們以此來代表喝啤酒)。此后直到mutex_unlock()就構成了另一個mutex結構。

那么如何讓前面十個調用cond_wait()的線程得到通知呢?我們注意到if還有另一種可能,也就是修建好第11個房間的人負責調用cond_broadcast()。它會給所有調用cond_wait()的線程放送通知,以便讓那些線程恢復運行。

 

Condition variable特別適用於多個線程等待某個條件的發生。如果不使用Condition variable,那么每個線程就需要不斷嘗試獲得mutex並檢查條件是否發生,這樣大大浪費了系統的資源。

 

1. 相關函數                                                                                         

 1        #include <pthread.h>
 2        pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
 3        int    pthread_cond_init(pthread_cond_t    *cond,    pthread_condattr_t
 4        *cond_attr);
 5        int pthread_cond_signal(pthread_cond_t *cond);
 6        int pthread_cond_broadcast(pthread_cond_t *cond);
 7        int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
 8        int   pthread_cond_timedwait(pthread_cond_t   *cond,    pthread_mutex_t
 9        *mutex, const struct timespec *abstime);
10        int pthread_cond_destroy(pthread_cond_t *cond);

 

                                                                                          
2. 說明
    條件變量是一種同步機制,允許線程掛起,直到共享數據上的某些條件得到滿足。條件變量上的基本操作有:觸發條件(當條件變為 true 時);等待條件,掛起線程直到其他線程觸發條件。
    條件變量要和互斥量相聯結,以避免出現條件競爭--一個線程預備等待一個條件變量,當它在真正進入等待之前,另一個線程恰好觸發了該條件。
    pthread_cond_init 使用 cond_attr 指定的屬性初始化條件變量 cond,當 cond_attr 為 NULL 時,使用缺省的屬性。LinuxThreads 實現條件變量不支持屬性,因此 cond_attr 參數實際被忽略。
    pthread_cond_t 類型的變量也可以用 PTHREAD_COND_INITIALIZER 常量進行靜態初始化。                                                                                          
    pthread_cond_signal 使在條件變量上等待的線程中的一個線程重新開始。如果沒有等待的線程,則什么也不做。如果有多個線程在等待該條件,只有一個能重啟動,但不能指定哪一個。
    pthread_cond_broadcast 重啟動等待該條件變量的所有線程。如果沒有等待的線程,則什么也不做。
    pthread_cond_wait 自動解鎖互斥量(如同執行了 pthread_unlock_mutex),並等待條件變量觸發。這時線程掛起,不占用 CPU 時間,直到條件變量被觸發。在調用 pthread_cond_wait 之前,應用程序必須加鎖互斥量。pthread_cond_wait 函數返回前,自動重新對互斥量加鎖(如同執行了 pthread_lock_mutex)。
    互斥量的解鎖和在條件變量上掛起都是自動進行的。因此,在條件變量被觸發前,如果所有的線程都要對互斥量加鎖,這種機制可保證在線程加鎖互斥量和進入等待條件變量期間,條件變量不被觸發。
    pthread_cond_timedwait 和 pthread_cond_wait 一樣,自動解鎖互斥量及等待條件變量,但它還限定了等待時間。如果在 abstime 指定的時間內 cond 未觸發,互斥量 mutex 被重新加鎖,且 pthread_cond_timedwait 返回錯誤 ETIMEDOUT。abstime 參數指定一個絕對時間,時間原點與 time 和 gettimeofday 相同:abstime = 0 表示 1970 年 1 月 1 日 00:00:00 GMT。
    pthread_cond_destroy 銷毀一個條件變量,釋放它擁有的資源。進入 pthread_cond_destroy 之前,必須沒有在該條件變量上等待的線程。在 LinuxThreads 的實現中,條件變量不聯結資源,除檢查有沒有等待的線程外,pthread_cond_destroy 實際上什么也不做。
3. 取消                                                                                          
    pthread_cond_wait 和 pthread_cond_timedwait 是取消點。如果一個線程在這些函數上掛起時被取消,線程立即繼續執行,然后再次對 pthread_cond_wait 和 pthread_cond_timedwait 在 mutex 參數加鎖,最后執行取消。因此,當調用清除處理程序時,可確保,mutex 是加鎖的。
4. 異步信號安全(Async-signal Safety)                                                                                          
    條件變量函數不是異步信號安全的,不應當在信號處理程序中進行調用。特別要注意,如果在信號處理程序中調用 pthread_cond_signal 或 pthread_cond_boardcast 函數,可能導致調用線程死鎖。
                                                                                          
5. 返回值
    在執行成功時,所有條件變量函數都返回 0,錯誤時返回非零的錯誤代碼。
6. 錯誤代碼
    pthread_cond_init,   pthread_cond_signal,  pthread_cond_broadcast, 和 pthread_cond_wait 從不返回錯誤代碼。
    pthread_cond_timedwait 函數出錯時返回下列錯誤代碼:
        ETIMEDOUT   abstime 指定的時間超時時,條件變量未觸發
        EINTR       pthread_cond_timedwait 被觸發中斷
    pthread_cond_destroy 函數出錯時返回下列錯誤代碼:
        EBUSY       某些線程正在等待該條件變量
 7. 舉例      

 1  2     設有兩個共享的變量 x 和 y,通過互斥量 mut 保護,當 x > y 時,條件變量 cond 被觸發。
 3              int x,y;
 4              int x,y;
 5              pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;
 6              pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
 7                                                                                           
 8     等待直到 x > y 的執行流程:
 9               pthread_mutex_lock(&mut);
10               while (x <= y) {
11                       pthread_cond_wait(&cond, &mut);
12               }
13               /* 對 x、y 進行操作 */
14               pthread_mutex_unlock(&mut);
15                                                                                           
16      對 x 和 y 的修改可能導致 x > y,應當觸發條件變量:                                                                                          
17               pthread_mutex_lock(&mut);
18               /* 修改 x、y */
19               if (x > y) pthread_cond_broadcast(&cond);
20               pthread_mutex_unlock(&mut);

 

                                                                                   
    如果能夠確定最多只有一個等待線程需要被喚醒(例如,如果只有兩個線程通過 x、y 通信),則使用 pthread_cond_signal 比 pthread_cond_broadcast 效率稍高一些。如果不能確定,應當用 pthread_cond_broadcast。
    要等待在 5 秒內 x > y,這樣處理:

 1                                                                                           
 2               struct timeval now;
 3               struct timespec timeout;
 4               int retcode;
 5                                                                                           
 6               pthread_mutex_lock(&mut);
 7               gettimeofday(&now);
 8               timeout.tv_sec = now.tv_sec + 5;
 9               timeout.tv_nsec = now.tv_usec * 1000;
10               retcode = 0;
11               while (x <= y && retcode != ETIMEDOUT) {
12                       retcode = pthread_cond_timedwait(&cond, &mut, &timeout);
13               }
14               if (retcode == ETIMEDOUT) {
15                       /* 發生超時 */
16               } else {
17                       /* 操作 x 和  y */
18               }
19               pthread_mutex_unlock(&mut);

 

---

  互斥鎖一個明顯的缺點是它只有兩種狀態:鎖定和非鎖定。而條件變量通過允許線程阻塞和等待另一個線程發送信號的方法彌補了互斥鎖的不足,它常和互斥鎖一起使用。使用時,條件變量被用來阻塞一個線程,當條件不滿足時,線程往往解開相應的互斥鎖並等待條件發生變化。一旦其它的某個線程改變了條件變量,它將通知相應的條件變量喚醒一個或多個正被此條件變量阻塞的線程。這些線程將重新鎖定互斥鎖並重新測試條件是否滿足。一般說來,條件變量被用來進行線程間的同步
  條件變量的結構為pthread_cond_t,函數pthread_cond_init()被用來初始化一個條件變量。它的原型為:
  extern int pthread_cond_init __P ((pthread_cond_t *__cond,__const pthread_condattr_t *__cond_attr));
  其中cond是一個指向結構pthread_cond_t的指針,cond_attr是一個指向結構pthread_condattr_t的指針。結構pthread_condattr_t是條件變量的屬性結構,和互斥鎖一樣可以用它來設置條件變量是進程內可用還是進程間可用,默認值是PTHREAD_ PROCESS_PRIVATE,即此條件變量被同一進程內的各個線程使用。注意初始化條件變量只有未被使用時才能重新初始化或被釋放。釋放一個條件變量的函數為pthread_cond_ destroy(pthread_cond_t cond)。 
  函數pthread_cond_wait()使線程阻塞在一個條件變量上。它的函數原型為:
  extern int pthread_cond_wait __P ((pthread_cond_t *__cond,
  pthread_mutex_t *__mutex));
  線程解開mutex指向的鎖並被條件變量cond阻塞線程可以被函數pthread_cond_signal和函數pthread_cond_broadcast喚醒,但是要注意的是,條件變量只是起阻塞和喚醒線程的作用,具體的判斷條件還需用戶給出,例如一個變量是否為0等等,這一點從后面的例子中可以看到。線程被喚醒后,它將重新檢查判斷條件是否滿足,如果還不滿足,一般說來線程應該仍阻塞在這里,被等待被下一次喚醒。這個過程一般用while語句實現。
  另一個用來阻塞線程的函數是pthread_cond_timedwait(),它的原型為:
  extern int pthread_cond_timedwait __P ((pthread_cond_t *__cond,
  pthread_mutex_t *__mutex, __const struct timespec *__abstime));
  它比函數pthread_cond_wait()多了一個時間參數,經歷abstime段時間后,即使條件變量不滿足,阻塞也被解除。
  函數pthread_cond_signal()的原型為:
  extern int pthread_cond_signal __P ((pthread_cond_t *__cond));
  它用來釋放被阻塞在條件變量cond上的一個線程。多個線程阻塞在此條件變量上時,哪一個線程被喚醒是由線程的調度策略所決定的。要注意的是,必須用保護條件變量的互斥鎖來保護這個函數,否則條件滿足信號有可能在測試條件和調用pthread_cond_wait函數之間被發出,從而造成無限制的等待。下面是使用函數pthread_cond_wait()和函數pthread_cond_signal()的一個簡單的例子。

 1 pthread_mutex_t count_lock;
 2 pthread_cond_t count_nonzero;
 3 unsigned count;
 4 decrement_count () {
 5     pthread_mutex_lock (&count_lock);
 6     while(count==0) 
 7         pthread_cond_wait( &count_nonzero, &count_lock);
 8     count=count -1;
 9     pthread_mutex_unlock (&count_lock);
10 }
11 
12 increment_count(){
13     pthread_mutex_lock(&count_lock);
14     if(count==0)
15         pthread_cond_signal(&count_nonzero);
16     count=count+1;
17     pthread_mutex_unlock(&count_lock);
18 }

 

  count值為0時,decrement函數在pthread_cond_wait處被阻塞,並打開互斥鎖count_lock。此時,當調用到函數increment_count時,pthread_cond_signal()函數改變條件變量,告知decrement_count()停止阻塞。
  函數pthread_cond_broadcast(pthread_cond_t *cond)用來喚醒所有被阻塞在條件變量cond上的線程。這些線程被喚醒后將再次競爭相應的互斥鎖,所以必須小心使用這個函數。

3) reader-writer lock

Reader-writer lock與mutex非常相似。r、RW lock有三種狀態: 共享讀取鎖(shared-read)互斥寫入鎖(exclusive-write lock),打開(unlock)。后兩種狀態與之前的mutex兩種狀態完全相同。

一個unlock的RW lock可以被某個線程獲取R鎖或者W鎖。

如果被一個線程獲得R鎖,RW lock可以被其它線程繼續獲得R鎖,而不必等待該線程釋放R鎖。但是,如果此時有其它線程想要獲得W鎖,它必須等到所有持有共享讀取鎖的線程釋放掉各自的R鎖。

如果一個鎖被一個線程獲得W鎖,那么其它線程,無論是想要獲取R鎖還是W鎖,都必須等待該線程釋放W鎖。

這樣,多個線程就可以同時讀取共享資源。而具有危險性的寫入操作則得到了互斥鎖的保護。

 

我們需要同步並發系統,這為程序員編程帶來了難度。但是多線程系統可以很好的解決許多IO瓶頸的問題。比如我們監聽網絡端口。如果我們只有一個線程,那么我們必須監聽,接收請求,處理,回復,再監聽。如果我們使用多線程系統,則可以讓多個線程監聽。當我們的某個線程進行處理的時候,我們還可以有其他的線程繼續監聽,這樣,就大大提高了系統的利用率。在數據越來越大,服務器讀寫操作越來越多的今天,這具有相當的意義。多線程還可以更有效地利用多CPU的環境。

(就像做飯一樣,不斷切換去處理不同的菜。)

 

本文中所使用的程序采用偽C的寫法。不同的語言有不同的函數名(比如mutex_lock)。這里關注的是邏輯上的概念,而不是具體的實現和語言規范。

 

4) 信號燈

 

  信號燈與互斥鎖和條件變量的主要不同在於"燈"的概念,燈亮則意味着資源可用,燈滅則意味着不可用。如果說后兩中同步方式側重於"等待"操作,即資源不可用的話,信號燈機制則側重於點燈,即告知資源可用;沒有等待線程的解鎖或激發條件都是沒有意義的,而沒有等待燈亮的線程的點燈操作則有效,且能保持燈亮狀態。當然,這樣的操作原語也意味着更多的開銷。

 

   信號燈的應用除了燈亮/燈滅這種二元燈以外,也可以采用大於1的燈數,以表示資源數大於1,這時可以稱之為多元燈。

 

1. 創建和注銷

 

   POSIX信號燈標准定義了有名信號燈和無名信號燈兩種,但LinuxThreads的實現僅有無名燈,同時有名燈除了總是可用於多進程之間以外,在使用上與無名燈並沒有很大的區別,因此下面僅就無名燈進行討論。

 

    int sem_init(sem_t *sem, int pshared, unsigned int value)

 


這是創建信號燈的API,其中value為信號燈的初值,pshared表示是否為多進程共享而不僅僅是用於一個進程。LinuxThreads沒有實現多進程共享信號燈,因此所有非0值的pshared輸入都將使sem_init()返回-1,且置errno為ENOSYS。初始化好的信號燈由sem變量表征,用於以下點燈、滅燈操作。

 

    int sem_destroy(sem_t * sem) 

 


被注銷的信號燈sem要求已沒有線程在等待該信號燈,否則返回-1,且置errno為EBUSY。除此之外,LinuxThreads的信號燈注銷函數不做其他動作。

 

2. 點燈和滅燈

 

    int sem_post(sem_t * sem)

 

 

點燈操作將信號燈值原子地加1,表示增加一個可訪問的資源。

 

    int sem_wait(sem_t * sem)
    int sem_trywait(sem_t * sem)

 


 

 

sem_wait()為等待燈亮操作,等待燈亮(信號燈值大於0),然后將信號燈原子地減1,並返回。sem_trywait()為sem_wait()的非阻塞版,如果信號燈計數大於0,則原子地減1並返回0,否則立即返回-1,errno置為EAGAIN。

 

3. 獲取燈值

 

    int sem_getvalue(sem_t * sem, int * sval)

 

 

讀取sem中的燈計數,存於*sval中,並返回0。

 

4. 其他

 

sem_wait()被實現為取消點,而且在支持原子"比較且交換"指令的體系結構上,sem_post()是唯一能用於異步信號處理函數的POSIX異步信號安全的API。

 

--------------

  信號量本質上是一個非負的整數計數器,它被用來控制對公共資源的訪問。當公共資源增加時,調用函數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
  下面來看一個使用信號量的例子。在這個例子中,一共有4個線程,其中兩個線程負責從文件讀取數據到公共的緩沖區,另兩個線程從緩沖區讀取數據作不同的處理(加和乘運算)。

 1 /* File sem.c */
 2 #include <stdio.h>
 3 #include <pthread.h>
 4 #include <semaphore.h>
 5 #define MAXSTACK 100
 6 int stack[MAXSTACK][2];
 7 int size=0;
 8 sem_t sem;
 9 
10 /* 從文件1.dat讀取數據,每讀一次,信號量加一*/
11 void ReadData1(void){
12 FILE *fp=fopen("1.dat","r");
13 while(!feof(fp)){
14 fscanf(fp,"%d %d",&stack[size][0],&stack[size][1]);
15 sem_post(&sem);
16 ++size;
17 }
18 fclose(fp);
19 }
20 
21 /*從文件2.dat讀取數據*/
22 void ReadData2(void){
23 FILE *fp=fopen("2.dat","r");
24 while(!feof(fp)){
25 fscanf(fp,"%d %d",&stack[size][0],&stack[size][1]);
26 sem_post(&sem);
27 ++size;
28 }
29 fclose(fp);
30 }
31 
32 /*阻塞等待緩沖區有數據,讀取數據后,釋放空間,繼續等待*/
33 void HandleData1(void){
34 while(1){
35 sem_wait(&sem);
36 printf("Plus:%d+%d=%d\n",stack[size][0],stack[size][1],
37 stack[size][0]+stack[size][1]);
38 --size;
39 }
40 }
41 
42 void HandleData2(void){
43 while(1){
44 sem_wait(&sem);
45 printf("Multiply:%d*%d=%d\n",stack[size][0],stack[size][1],
46 stack[size][0]*stack[size][1]);
47 --size;
48 }
49 }
50 
51 int main(void){
52 pthread_t t1,t2,t3,t4;
53 sem_init(&sem,0,0);
54 pthread_create(&t1,NULL,(void *)HandleData1,NULL);
55 pthread_create(&t2,NULL,(void *)HandleData2,NULL);
56 pthread_create(&t3,NULL,(void *)ReadData1,NULL);
57 pthread_create(&t4,NULL,(void *)ReadData2,NULL);
58 /* 防止程序過早退出,讓它在此無限期等待*/
59 pthread_join(t1,NULL);
60 }

 

  在Linux下,用命令gcc -lpthread sem.c -o sem生成可執行文件sem。事先編輯好數據文件1.dat和2.dat,假設它們的內容分別為1 2 3 4 5 6 7 8 9 10和 -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 ,運行sem,得到如下的結果:
Multiply:-1*-2=2
Plus:-1+-2=-3
Multiply:9*10=90
Plus:-9+-10=-19
Multiply:-7*-8=56
Plus:-5+-6=-11
Multiply:-3*-4=12
Plus:9+10=19
Plus:7+8=15
Plus:5+6=11

  從中可以看出各個線程間的競爭關系。而數值並未按原先的順序顯示出來,這是由於size這個數值被各個線程任意修改的緣故。這也往往是多線程編程要注意的問題。

 

五) 信號機制

 

在Linux的多線程中使用信號機制,與在進程中使用信號機制有着根本的區別,可以說是完全不同。在進程環境中,對信號的處理是,先注冊信號處理函數,當信號異步發生時,調用處理函數來處理信號。它完全是異步的(我們完全不知到信號會在進程的那個執行點到來!)。然而信號處理函數的實現,有着許多的限制;比如有一些函數不能在信號處理函數中調用;再比如一些函數read、recv等調用時會被異步的信號給中斷(interrupt),因此我們必須對在這些函數在調用時因為信號而中斷的情況進行處理(判斷函數返回時 enno 是否等於 EINTR)。


但是在多線程中處理信號的原則卻完全不同,它的基本原則是:將對信號的異步處理,轉換成同步處理,也就是說用一個線程專門的來“同步等待”信號的到來,而其它的線程可以完全不被該信號中斷/打斷(interrupt)。這樣就在相當程度上簡化了在多線程環境中對信號的處理。而且可以保證其它的線程不受信號的影響。這樣我們對信號就可以完全預測,因為它不再是異步的,而是同步的 我們完全知道信號會在哪個線程中的哪個執行點到來而被處理! 。而同步的編程模式總是比異步的編程模式簡單。其實多線程相比於多進程的其中一個優點就是:多線程可以將進程中異步的東西轉換成同步的來處理。
 
1. sigwait函數:
sigwait - wait for a signal
#include
int sigwait(const sigset_t *set, int *sig);

Description
The sigwait() function suspends execution of the calling thread until the delivery of one 
of the signals specified in the signal set set. The function accepts the signal (removes 
it from the pending list of signals), and returns the signal number in sig.
The operation of sigwait() is the same as sigwaitinfo(2), except that:
* sigwait() only returns the signal number, rather than a siginfo_t structure describing 
  the signal.
* The return values of the two functions are different.
Return Value
On success, sigwait() returns 0. On error, it returns a positive error number.

 

從上面的man sigwait的描述中,我們知道:sigwait是同步的等待信號的到來,而不是像進程中那樣是異步的等待信號的到來。sigwait函數使用一個信號集作為他的參數,並且在集合中的任一個信號發生時返回該信號值,解除阻塞,然后可以針對該信號進行一些相應的處理。
2. 記住:
     在多線程代碼中,總是使用sigwait或者sigwaitinfo或者sigtimedwait等函數來處理信號。
     而不是signal或者sigaction等函數。因為在一個線程中調用signal或者sigaction等函數會改變所以線程中的
     信號處理函數。而不是僅僅改變調用signal/sigaction的那個線程的信號處理函數。
3. pthread_sigmask函數:
    每個線程均有自己的信號屏蔽集(信號掩碼),可以使用pthread_sigmask函數來屏蔽某個線程對某些信號的
   響應處理,僅留下需要處理該信號的線程來處理指定的信號。實現方式是:利用線程信號屏蔽集的繼承關系
  (在主進程中對sigmask進行設置后,主進程創建出來的線程將繼承主進程的掩碼
pthread_sigmask - examine and change mask of blocked signals
#include
int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset);
Compile and link with -pthread.
DESCRIPTION
The pthread_sigmask() function is just like sigprocmask(2), with the difference that its use
in multithreaded programs is explicitly specified by POSIX.1-2001.
Other differences are noted in this page.
For a description of the arguments and operation of this function, see sigprocmask(2).
RETURN VALUE
On success, pthread_sigmask() returns 0; on error, it returns an error number.
NOTES
A new thread inherits a copy of its creator's signal mask.
(from man sigprocmask: )
The behavior of the call is dependent on the value of how, as follows.
SIG_BLOCK
The set of blocked signals is the union of the current set and the set argument.
SIG_UNBLOCK
The signals in set are removed from the current set of blocked signals. It is permissible 
to attempt to unblock a signal which is not blocked.
SIG_SETMASK
The set of blocked signals is set to the argument set.
If oldset is non-NULL, the previous value of the signal mask is stored in oldset.
If set is NULL, then the signal mask is unchanged (i.e., how is ignored), but the current 
value of the signal mask is nevertheless returned in oldset (if it is not NULL).

 

4. pthread_kill函數:
   在多線程程序中,一個線程可以使用pthread_kill對同一個進程中指定的線程(包括自己)發送信號。注意在多線程中  
  一般不使用kill函數發送信號,因為kill是對進程發送信號,結果是:正在運行的線程會處理該信號,如果該線程沒有
 注冊信號處理函數,那么會導致整個進程退出。
#include
int pthread_kill(pthread_t thread, int sig);
Compile and link with -pthread.
DESCRIPTION
The pthread_kill() function sends the signal sig to thread, another thread in the same 
process as the caller. The signal is asynchronously directed to thread.
If sig is 0, then no signal is sent, but error checking is still performed; this can be 
used to check for the existence of a thread ID.
RETURN VALUE
On success, pthread_kill() returns 0; on error, it returns an error number, and no signal 
is sent.
ERRORS
ESRCH No thread with the ID thread could be found.
EINVAL An invalid signal was specified.

 

5. 記住:調用sigwait同步等待的信號必須在調用線程中被屏蔽,並且通常應該在所有的線程中被屏蔽(這樣可以保證信號絕不會被送到除了調用sigwait的任何其它線程),這是通過利用信號掩碼的繼承關系來達到的。
(The semantics of sigwait require that all threads (including the thread calling sigwait) have the signal masked, for
  reliable operation. Otherwise, a signal that arrives not blocked in sigwait  might be  delivered to another thread. )
6. 代碼示例:(from man pthread_sigmask)
 1 #include <</SPAN>pthread.h>
 2 #include <</SPAN>stdio.h>
 3 #include <</SPAN>stdlib.h>
 4 #include <</SPAN>unistd.h>
 5 #include <</SPAN>signal.h>
 6 #include <</SPAN>errno.h>
 7  
 8 /* Simple error handling functions */
 9  
10 #define handle_error_en(en, msg) \
11         do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)
12  
13 static void *  
14 sig_thread(void *arg)
15 {
16       sigset_t *set = (sigset_t *) arg;
17       int s, sig;
18  
19       for (;;) {
20             s = sigwait(set, &sig);
21             if (s != 0)
22                   handle_error_en(s, "sigwait");
23             printf("Signal handling thread got signal %d\n", sig);
24       }
25 }
26  
27 int  
28 main(int argc, char *argv[])
29 {
30       pthread_t thread;
31       sigset_t set;
32       int s;
33  
34       /* 
35          Block SIGINT; other threads created by main() will inherit
36          a copy of the signal mask. 
37        */
38       sigemptyset(&set);
39       sigaddset(&set, SIGQUIT);
40       sigaddset(&set, SIGUSR1);
41       s = pthread_sigmask(SIG_BLOCK, &set, NULL);
42       if (s != 0)
43             handle_error_en(s, "pthread_sigmask");
44       s = pthread_create(&thread, NULL, &sig_thread, (void *) &set);
45       if (s != 0)
46             handle_error_en(s, "pthread_create");
47       /* 
48         Main thread carries on to create other threads and/or do
49         other work 
50        */
51       pause(); /* Dummy pause so we can test program */
52       return 0;
53 }

 

編譯運行情況:
 1 digdeep@ubuntu:~/pthread/learnthread$ gcc -Wall -pthread -o pthread_sigmask pthread_sigmask.c 
 2 digdeep@ubuntu:~/pthread/learnthread$ ./pthread_sigmask &
 3 [1] 4170
 4 digdeep@ubuntu:~/pthread/learnthread$ kill -QUIT %1
 5 digdeep@ubuntu:~/pthread/learnthread$ Signal handling thread got signal 3
 6 digdeep@ubuntu:~/pthread/learnthread$ kill -USR1 %1
 7 digdeep@ubuntu:~/pthread/learnthread$ Signal handling thread got signal 10
 8 digdeep@ubuntu:~/pthread/learnthread$ kill -TERM %1
 9 digdeep@ubuntu:~/pthread/learnthread$ 
10 [1]+ Terminated ./pthread_sigmask
11 digdeep@ubuntu:~/pthread/learnthread$

 

這個例子演示了:通過在主線程中阻塞一些信號,其它的線程會繼承信號掩碼,然后專門用一個線程使用sigwait函數來同步的處理信號,使其它的線程不受到信號的影響。

 

   由於LinuxThreads是在核外使用核內輕量級進程實現的線程,所以基於內核的異步信號操作對於線程也是有效的。但同時,由於異步信號總是實際發往某個進程,所以無法實現POSIX標准所要求的"信號到達某個進程,然后再由該進程將信號分發到所有沒有阻塞該信號的線程中"原語,而是只能影響到其中一個線程。

 --------

   POSIX異步信號同時也是一個標准C庫提供的功能,主要包括信號集管理(sigemptyset()、sigfillset()、sigaddset()、sigdelset()、sigismember()等)、信號處理函數安裝(sigaction())、信號阻塞控制(sigprocmask())、被阻塞信號查詢(sigpending())、信號等待(sigsuspend())等,它們與發送信號的kill()等函數配合就能實現進程間異步信號功能。LinuxThreads圍繞線程封裝了sigaction()何raise(),本節集中討論LinuxThreads中擴展的異步信號函數,包括pthread_sigmask()、pthread_kill()和sigwait()三個函數。毫無疑問,所有POSIX異步信號函數對於線程都是可用的。

 

int pthread_sigmask(int how, const sigset_t *newmask, sigset_t *oldmask)
設置線程的信號屏蔽碼,語義與sigprocmask()相同,但對不允許屏蔽的Cancel信號和不允許響應的Restart信號進行了保護。被屏蔽的信號保存在信號隊列中,可由sigpending()函數取出。

 

int pthread_kill(pthread_t thread, int signo)
向thread號線程發送signo信號。實現中在通過thread線程號定位到對應進程號以后使用kill()系統調用完成發送。

 

int sigwait(const sigset_t *set, int *sig)
掛起線程,等待set中指定的信號之一到達,並將到達的信號存入*sig中。POSIX標准建議在調用sigwait()等待信號以前,進程中所有線程都應屏蔽該信號,以保證僅有sigwait()的調用者獲得該信號,因此,對於需要等待同步的異步信號,總是應該在創建任何線程以前調用pthread_sigmask()屏蔽該信號的處理。而且,調用sigwait()期間,原來附接在該信號上的信號處理函數不會被調用。

 

如果在等待期間接收到Cancel信號,則立即退出等待,也就是說sigwait()被實現為取消點。

 


 

 

六) 其他同步方式

 

   除了上述討論的同步方式以外,其他很多進程間通信手段對於LinuxThreads也是可用的,比如基於文件系統的IPC(管道、Unix域Socket等)、消息隊列(Sys.V或者Posix的)、System V的信號燈等。只有一點需要注意,LinuxThreads在核內是作為共享存儲區、共享文件系統屬性、共享信號處理、共享文件描述符的獨立進程看待的。

 

 

 

總結:

 

1).互斥鎖必須總是由給它上鎖的線程解鎖,信號量的掛出即不必由執行過它的等待操作的同一進程執行。一個線程可以等待某個給定信號燈,而另一個線程可以掛出該信號燈。

 

2).互斥鎖要么鎖住,要么被解開(二值狀態,類型二值信號量)

 

3).由於信號量有一個與之關聯的狀態(它的計數值),信號量掛出操作總是被記住。然而當向一個條件變量發送信號時,如果沒有線程等待在該條件變量上,那么該信號將丟失。

 

4).互斥鎖是為了上鎖而設計的,條件變量是為了等待而設計的,信號燈即可用於上鎖,也可用於等待,因而可能導致更多的開銷和更高的復雜性。

 --------------

Linux系統中的進程通信方式主要以下幾種:

同一主機上的進程通信方式

   * UNIX進程間通信方式: 包括管道(PIPE), 有名管道(FIFO), 和信號(Signal)

   * System V進程通信方式:包括信號量(Semaphore), 消息隊列(Message Queue), 和共享內存(Shared Memory)

網絡主機間的進程通信方式

   * RPC: Remote Procedure Call 遠程過程調用

   * Socket: 當前最流行的網絡通信方式, 基於TCP/IP協議的通信方式.

 

各自的特點:

  • 管道(PIPE):管道是一種半雙工的通信方式,數據只能單向流動,而且只能在具有親緣關系(父子進程)的進程間使用。另外管道傳送的是無格式的字節流,並且管道緩沖區的大小是有限的(管道緩沖區存在於內存中,在管道創建時,為緩沖區分配一個頁面大小)。
  • 有名管道 (FIFO): 有名管道也是半雙工的通信方式,但是它允許無親緣關系進程間的通信。
  • 信號(Signal): 信號是一種比較復雜的通信方式,用於通知接收進程某個事件已經發生。
  • 信號量(Semaphore):信號量是一個計數器,可以用來控制多個進程對共享資源的訪問。它常作為一種鎖機制,防止某進程正在訪問共享資源時,其他進程也訪問該資源。因此,主要作為進程間以及同一進程內不同線程之間的同步手段。
  • 消息隊列(Message Queue):消息隊列是由消息的鏈表,存放在內核中並由消息隊列標識符標識。消息隊列克服了信號傳遞信息少、管道只能承載無格式字節流以及緩沖區大小受限等缺點。
  • 共享內存(Shared Memory ):共享內存就是映射一段能被其他進程所訪問的內存,這段共享內存由一個進程創建,但多個進程都可以訪問。共享內存是最快的 IPC 方式,它是針對其他進程間通信方式運行效率低而專門設計的。它往往與其他通信機制,如信號量,配合使用,來實現進程間的同步和通信。
  • 套接字(Socket): 套解口也是一種進程間通信機制,與其他通信機制不同的是,它可用於不同主機間的進程通信。

 

Linux系統中的線程通信方式主要以下幾種:

*  鎖機制:包括互斥鎖、條件變量、讀寫鎖

   互斥鎖提供了以排他方式防止數據結構被並發修改的方法。

   使用條件變量可以以原子的方式阻塞進程,直到某個特定條件為真為止。對條件的測試是在互斥鎖的保護下進行的。條件變量始終與互斥鎖一起使用。

   讀寫鎖允許多個線程同時讀共享數據,而對寫操作是互斥的。

*  信號量機制(Semaphore):包括無名線程信號量和命名線程信號量

*  信號機制(Signal):類似進程間的信號處理

 


免責聲明!

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



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