一.概述 上一篇,介紹了互斥量。條件變數與互斥量不同,互斥量是防止多線程同時訪問共用的互斥變數來保護臨界區。條件變數是多線程間可以通過它來告知其他線程某個狀態發生了改變,讓等待在這個條件變數的線程繼續執行。通俗一點來講:設置一個條件變數讓線程1等待在一...
一.概述
上一篇,介紹了互斥量。條件變數與互斥量不同,互斥量是防止多線程同時訪問共用的互斥變數來保護臨界區。條件變數是多線程間可以通過它來告知其他線程某個狀態發生了改變,讓等待在這個條件變數的線程繼續執行。通俗一點來講:設置一個條件變數讓線程1等待在一個臨界區的前面,當其他線程給這個變數執行通知操作時,線程1才會被喚醒,繼續向下執行。
條件變數總是和互斥量一起使用,互斥量保護著條件變數,防止多個線程對條件變數產生競爭。等會寫個小例子,看它們如何一起合作!
二.函數介面
1.初始化條件變數
1.1:巨集常量初始化
1 pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
1.2:函數初始化
1 #include <pthread.h> 2 3 int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
跟互斥量類似,cond是條件變數的結構指針,attr是條件變數屬性的結構指針。
2.等待和通知條件變數
1 #include <pthread.h> 2 3 int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime); 4 int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex); 5 6 int pthread_cond_broadcast(pthread_cond_t *cond); 7 int pthread_cond_signal(pthread_cond_t *cond);
等待函數裡面,要傳入一個互斥量。pthread_cond_timewait()可以指定一個時間來等待,如果規定的時間沒有獲得通知,就返回ETIMEDOUT錯誤。而pthread_cond_wait()會一直阻塞。
通知函數,pthread_cond_signal()至少喚醒一個等待的線程,pthread_cond_broadcast()會喚醒在該條件變數上所有線程。
3.銷毀條件變數
1 #include <pthread.h> 2 3 int pthread_cond_destroy(pthread_cond_t *cond);
三.簡單的例子
我們還是用上一篇互斥量的例子。單獨使用互斥量時,有些線程要獲取某個狀態的成立,需要多次進出臨界區,對互斥量頻繁加鎖解鎖造成系統資源的浪費。下麵結合條件變數來解決這個問題:
1 /** 2 * @file pthread_mutex.c 3 */ 4 5 #include <stdio.h> 6 #include <stdlib.h> 7 #include <string.h> 8 #include <unistd.h> 9 #include <pthread.h> 10 11 /* 定義互斥量 */ 12 pthread_mutex_t mtx; 13 /* 互斥量屬性 */ 14 pthread_mutexattr_t mtx_attr; 15 /* 全局資源 */ 16 int money; 17 18 /* 條件變數 */ 19 pthread_cond_t cond = PTHREAD_COND_INITIALIZER; 20 21 void err_exit(const char *err_msg) 22 { 23 printf("error:%s\n", err_msg); 24 exit(1); 25 } 26 27 /* 線程函數 */ 28 void *thread_fun(void *arg) 29 { 30 while (1) 31 { 32 /* 加鎖 */ 33 pthread_mutex_lock(&mtx); 34 35 /* 條件變數 */ 36 while (money > 0) 37 { 38 printf("子線程坐等money等於0...\n"); 39 pthread_cond_wait(&cond, &mtx); 40 } 41 42 printf("子線程進入臨界區查看money\n"); 43 if (money == 0) 44 { 45 money += 200; 46 printf("子線程:money = %d\n", money); 47 } 48 49 /* 解鎖 */ 50 pthread_mutex_unlock(&mtx); 51 52 sleep(1); 53 } 54 55 return NULL; 56 } 57 58 int main(void) 59 { 60 pthread_t tid; 61 62 /* 初始化互斥量屬性 */ 63 if (pthread_mutexattr_init(&mtx_attr) == -1) 64 err_exit("pthread_mutexattr_init()"); 65 66 /* 設置互斥量屬性 */ 67 if (pthread_mutexattr_settype(&mtx_attr, PTHREAD_MUTEX_NORMAL) == -1) 68 err_exit("pthread_mutexattr_settype()"); 69 70 /* 初始化互斥量 */ 71 if (pthread_mutex_init(&mtx, &mtx_attr) == -1) 72 err_exit("pthread_mutex_init()"); 73 74 /* 創建一個線程 */ 75 if (pthread_create(&tid, NULL, thread_fun, NULL)== -1) 76 err_exit("pthread_create()"); 77 78 money = 1000; 79 while (1) 80 { 81 /* 加鎖 */ 82 pthread_mutex_lock(&mtx); 83 84 if (money > 0) 85 { 86 money -= 100; 87 printf("主線程:money = %d\n", money); 88 } 89 90 /* 解鎖 */ 91 pthread_mutex_unlock(&mtx); 92 93 /* 如果money = 1,就通知子線程 */ 94 if (money == 0) 95 { 96 printf("通知子線程\n"); 97 pthread_cond_signal(&cond); 98 } 99 100 sleep(1); 101 } 102 103 return 0; 104 }
代碼跟上一個例子幾乎一樣,就加了一個條件變數。編譯運行:
可以看到第39行的等待條件變數觸發後,子線程會一直等待,直到主線程通知它。這樣子線程就不會頻繁進入臨界區,頻繁加鎖解鎖。
四.深入知識
1.等待函數裡面要傳入一個互斥量,這個互斥量會在這個函數調用時會發生如下變化:函數剛剛被調用時,會把這個互斥量解鎖,然後讓調用線程阻塞,解鎖後其他線程才有機會獲得這個鎖。當某個線程調用通知函數時,這個函數收到通知後,又把互斥量加鎖,然後繼續向下操作臨界區。可見這個設計是非常合理的!!!
2.條件變數的等待函數用while迴圈包圍,本程式的第36行。原因:如果有多個線程都在等待這個條件變數關聯的互斥量,當條件變數收到通知,它下一步就是要鎖住這個互斥量,但在這個極小的時間差裡面,其他線程搶先獲取了這互斥量併進入臨界區把某個狀態改變了。此時這個條件變數應該繼續判斷別人剛剛搶先修改的狀態,即繼續執行while的判斷。還有一個原因時防止虛假通知,收到虛假通知後,只要while裡面的條件為真,就繼續休眠!!!