【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