1. 程式人生 > >【Linux 執行緒】執行緒同步《三》

【Linux 執行緒】執行緒同步《三》

1、條件變數

條件變數是利用執行緒間共享的全域性變數進行同步的一種機制,主要包括兩個動作:一個執行緒等待"條件變數的條件成立"而掛起;另一個執行緒使"條件成立"(給出條件成立訊號)。為了防止競爭,條件變數的使用總是和一個互斥

(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)互斥變數舉例:生產者&消費者

(4)舉例:

《舉例1》

 1 /*************************************************************************
 2     > File Name: pthread_cond1.c
 3     > Summary: 條件變數應用---生產者&消費者 version1 單個生產者&單個消費者情形
 4     > Author: xuelisheng 
 5     > Created Time: 2018年12月18日 
6 ************************************************************************/ 7 8 #include <stdio.h> 9 #include <unistd.h> 10 #include <malloc.h> 11 #include <pthread.h> 12 13 // 連結串列作為共享資料,需要被互斥量保護 14 struct msg{ 15 struct msg *next; 16 int num; 17 }; 18 19 struct msg *head;
20 21 // 靜態初始化,一個條件變數和一個互斥量 22 pthread_cond_t has_product = PTHREAD_COND_INITIALIZER; 23 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 24 25 void *consumer(void *p) 26 { 27 struct msg *mp; 28 for( ; ; ) 29 { 30 pthread_mutex_lock(&mutex); 31 while(head == NULL) // 說明此時沒有節點,這裡當只有一個消費者時,使用while和if都可以。如果有多個消費者則必須使用while 32 { 33 // 一開始 阻塞等待,並解鎖mutex 34 // 收到signal訊號之後,解除阻塞,加鎖mutex 35 pthread_cond_wait(&has_product, &mutex); 36 } 37 mp = head; 38 head = mp->next; // 消費掉一個產品 39 pthread_mutex_unlock(&mutex); 40 41 printf("Consume %lu --- %d\n", pthread_self(), mp->num); 42 free(mp); 43 sleep(rand()%5); // 休眠:為了列印效果明顯 44 } 45 } 46 47 void *producer(void *p) 48 { 49 struct msg *mp; 50 for(; ;) 51 { 52 mp = malloc(sizeof(struct msg)); 53 mp->num = rand() % 1000 + 1; // 模擬生產一個產品(1-1000之間的一個數字) 54 printf("Produce ---------------- %d\n", mp->num); 55 56 // 加互斥鎖:mp為共享資料 57 pthread_mutex_lock(&mutex); 58 mp->next = head; 59 head = mp; 60 pthread_mutex_unlock(&mutex); 61 62 pthread_cond_signal(&has_product); // 將等待在該條件變數上的一個執行緒喚醒,通知阻塞在條件變數上的執行緒 63 sleep(rand()%5); // 休眠:為了列印效果明顯 64 } 65 } 66 67 int main() 68 { 69 pthread_t pid, cid; 70 srand(time(NULL)); 71 72 // 建立執行緒 73 pthread_create(&pid, NULL, producer, NULL); 74 pthread_create(&pid, NULL, consumer, NULL); 75 76 // 等待回收 77 pthread_join(pid, NULL); 78 pthread_join(cid, NULL); 79 80 return 0; 81 }

執行結果(擷取部分):

Produce ---------------- 395
Consume 140093303256832 --- 395
Produce ---------------- 506
Consume 140093303256832 --- 506
Produce ---------------- 553
Consume 140093303256832 --- 553
Produce ---------------- 139
Produce ---------------- 758
Consume 140093303256832 --- 758
Produce ---------------- 313
Produce ---------------- 267
Consume 140093303256832 --- 267
Produce ---------------- 739
Consume 140093303256832 --- 739
Produce ---------------- 718
Consume 140093303256832 --- 718
Consume 140093303256832 --- 313
Produce ---------------- 744
Consume 140093303256832 --- 744
Consume 140093303256832 --- 139
Produce ---------------- 619
Produce ---------------- 449
Produce ---------------- 948
Produce ---------------- 276
Consume 140093303256832 --- 276
Produce ---------------- 896
Consume 140093303256832 --- 896
Consume 140093303256832 --- 948
Produce ---------------- 837
Produce ---------------- 317
Consume 140093303256832 --- 317
Produce ---------------- 478
Consume 140093303256832 --- 478
Consume 140093303256832 --- 837
Produce ---------------- 545
Consume 140093303256832 --- 545

 


《舉例2》

 1 /*************************************************************************
 2     > File Name: pthread_cond2.c
 3     > Summary: 條件變數應用---生產者&消費者 version2 單個生產者&多個消費者情形
 4     > Author: xuelisheng 
 5     > Created Time: 2018年12月18日 
 6  ************************************************************************/
 7 
 8 #include <stdio.h>
 9 #include <unistd.h>
10 #include <malloc.h>
11 #include <pthread.h>
12 
13 // 連結串列作為共享資料,需要被互斥量保護
14 struct msg{
15     struct msg *next;
16     int num;
17 };
18 
19 struct msg *head;
20 
21 // 靜態初始化,一個條件變數和一個互斥量
22 pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
23 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
24 
25 void *consumer(void *p)
26 {
27     struct msg *mp;
28     for( ; ; )
29     {
30         pthread_mutex_lock(&mutex);
31         while(head == NULL)                    // 說明此時沒有節點,這裡當只有一個消費者時,使用while和if都可以。如果有多個消費者則必須使用while
32         {
33              // 一開始 阻塞等待,並解鎖mutex
34              // 收到signal訊號之後,解除阻塞,加鎖mutex
35              pthread_cond_wait(&has_product, &mutex);               // 多個消費者執行緒都阻塞在這裡
36         }
37         mp = head;
38         head = mp->next;                                                                                  // 消費掉一個產品
39         pthread_mutex_unlock(&mutex);
40 
41         printf("Consume %lu --- %d\n", pthread_self(), mp->num);
42         free(mp);
43         sleep(rand()%5);                                                                                  // 休眠:為了列印效果明顯
44     }
45 }
46 
47  void *producer(void *p)
48  {
49      struct msg *mp;
50      for(; ;)
51      {
52          mp = malloc(sizeof(struct msg));
53          mp->num = rand() % 1000 + 1;                         // 模擬生產一個產品(1-1000之間的一個數字)
54          printf("Produce ---------------- %d\n", mp->num);
55 
56          // 加互斥鎖:mp為共享資料
57          pthread_mutex_lock(&mutex);
58          mp->next = head;
59          head = mp;
60          pthread_mutex_unlock(&mutex);
61 
62          pthread_cond_signal(&has_product);                            // 將等待在該條件變數上的一個執行緒喚醒,通知阻塞在條件變數上的執行緒
63          sleep(rand()%5);                                                                           // 休眠:為了列印效果明顯
64      }
65  }
66 
67  int main()
68  {
69      pthread_t pid, cid;
70      srand(time(NULL));
71 
72      // 建立執行緒
73      pthread_create(&pid, NULL, producer, NULL);
74 
75      // 建立多個消費者
76      pthread_create(&pid, NULL, consumer, NULL);
77      pthread_create(&pid, NULL, consumer, NULL);
78      pthread_create(&pid, NULL, consumer, NULL);
79      pthread_create(&pid, NULL, consumer, NULL);
80 
81      // 等待回收
82      pthread_join(pid, NULL);
83      pthread_join(cid, NULL);
84 
85      return 0;
86  }

執行結果(擷取部分):發現消費者執行緒id不同,即多個消費者

Produce ---------------- 913
Consume 139785213572864 --- 913
Produce ---------------- 509
Consume 139785213572864 --- 509
Produce ---------------- 970
Consume 139785292695296 --- 970
Produce ---------------- 3
Consume 139785196787456 --- 3
Produce ---------------- 41
Consume 139785205180160 --- 41
Produce ---------------- 917
Consume 139785213572864 --- 917
Produce ---------------- 417
Consume 139785196787456 --- 417
Produce ---------------- 768
Consume 139785292695296 --- 768
Produce ---------------- 354
Consume 139785213572864 --- 354
Produce ---------------- 706
Consume 139785205180160 --- 706
Produce ---------------- 412
Consume 139785292695296 --- 412
Produce ---------------- 359
Consume 139785196787456 --- 359
Produce ---------------- 144
Produce ---------------- 400
Consume 139785213572864 --- 144
Consume 139785205180160 --- 400
Produce ---------------- 809
Consume 139785213572864 --- 809