說到條件變量,首先說下互斥鎖,互斥鎖是最一種同步形式,用於保護臨界區,以保證任何時刻只有一個線程在執行其中的代碼(假設互斥鎖由多個線程共享),來保證共享數據的完整性,上鎖過程如下圖;
假如在一個程序中由3個線程訪問一個共享變量g_Count,其中線程1和線程是負責對g_Count變量加一,線程3是負責對g_Count變量減一;線程4是負責判斷g_Count是否大於等於100,是就將變量g_Count清零,代碼片段如下:
//線程1,2 while (1) { pthread_mutex_lock(&mutex); g_Count++; pthread_mutex_unlock(&mutex); sleep(1); }
//線程3 while (1) { pthread_mutex_lock(&mutex); g_Count--; pthread_mutex_unlock(&mutex); sleep(1); }
//線程3 while(1) { pthead_mutex_lock(&mutex); if (100 >= g_Count) { printf("g_Count >= 100\r\n"); g_Count = 0; pthread_mutex_unlock(&mutex); } else { pthread_mutex_unlock(&mutex); } sleep(1); }
上面這段代碼中的線程4並不知變量g_Count什么時候才會大於等於100,這就需要一直的循環判斷,但是每次的判斷都有上鎖和解鎖的操作(上鎖和操作是很費時的),這會帶來CPU資源浪費;對於這個問題可以使用條件變量處理;
條件變量是利用線程間共享的全局變量進行同步的一種機制,主要包括兩個動作:一個線程等待"條件變量的條件成立"而掛起;另一個線程使"條件成立"(給出條件成立信號);為了防止競爭,條件變量的使用總是和一個互斥鎖結合在一起;
條件變量是由互斥鎖保護的,線程在改變條件狀態前必須先鎖住互斥鎖,其他線程在獲得互斥量之前不會察覺到這種改變,因為必須鎖定互斥鎖才能計算條件;
將之前的代碼使用條件變量修改:
//線程1,2 while (1) { pthread_mutex_lock(&mutex); g_Count++; pthread_mutex_unlock(&mutex); if (g_Count >= 100) { pthead_cond_signal(&g_cond); } sleep(1); } //線程1,2 while (1) { pthread_mutex_lock(&mutex); g_Count--; pthread_mutex_unlock(&mutex); sleep(1); } //線程4 while(1) { pthead_mutex_lock(&mutex); while (g_Count < 100) { pthead_cond_wait(&g_cond, &mutex); } g_Count = 0; pthread_mutex_unlock(&mutex); sleep(1); }
在《UNIX環境高級編程》中,有這么一段:
;所以上面這段代碼中的線程3如果g_Count小於100,線程3會調用pthread_cond_wait,而pthread_cond_wait會釋放mutex,然后等待條件變為真返回,pthread_cond_wait返回時會再次鎖住mutex;條件不滿足時pthread_cond_wait會等待,從而不用一直的輪詢,減少CPU的浪費;
至於上面的代碼中的使用while的原因,在多核處理器下,pthread_cond_signal可能會激活多於一個線程(阻塞在條件變量上的線程);結果就是,當一個線程調用pthread_cond_signal()后,多個調用pthread_cond_wait()或pthread_cond_timedwait()的線程返回;這種效應就稱為“虛假喚醒”;
在pthread_cond_wait的man手冊中,對虛假喚醒有這樣一段話:
需要對條件進行再判斷以避免虛假喚醒:
如果用if判斷,多個等待線程在滿足if條件時都會被喚醒(虛假的),但實際上條件並不滿足,生產者生產出來的消費品已經被第一個線程消費了;
這就是我們使用while去做判斷而不是使用if的原因:因為等待在條件變量上的線程被喚醒有可能不是因為條件滿足而是由於虛假喚醒;所以我們需要對條件變量的狀態進行不斷檢查直到其滿足條件,不僅要在pthread_cond_wait前檢查條件是否成立,在pthread_cond_wait之后也要檢查;
參考:
https://blog.csdn.net/leeds1993/article/details/52738845
https://www.ibm.com/developerworks/cn/linux/thread/posix_thread3/