一 原因
最近在實現一個線程池的時候,須要用到POSIX中的cond和mutex進行線程間等待和同步,功能相似MS的同步對象Event。
發現cond和mutex的連用仍是挺不人性化的。說實話,MS在同步對象的API上,作得仍是至關不錯,文檔也很清晰。
Anyway,既然只能使用POSIX,就只能將就了。
我這個線程池在實現中碰到如下2個問題:
1 有n個線程等待一個事件。當有任務添加的時候,須要觸發其中一個線程啓動。
2 當線程池退出時,我須要觸發全部線程啓動,並檢測退出標誌,從而退出線程循環。
這個問題其實比初看上去要複雜,下面來分析
二 Windows上的實現
先介紹下Event同步對象,
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes, // pointer to security attributes
BOOL bManualReset, // flag for manual-reset event
BOOL bInitialState, // flag for initial state
LPCTSTR lpName // pointer to event-object name
);
第二個參數:bManualReset表示該對象爲人工仍是自動變量。咱們重點就是討論這個,該值決定如下幾個特色:
- 當Event爲人工變量時,一旦被觸發,則一直保持觸發狀態,觸發調用ResetEvent重置狀態。假設該值被觸發,那麼調用WaitXXX函數的全部線程都不該該阻塞在該事件上。
- 當Event爲自動變量時,一旦被觸發,若是有線程在等,那麼只會啓動其中一個線程(只會啓動一個線程,由於該值在啓動一個線程後會重置)。若是沒有線程在等,那麼該值一直保持觸發狀態
從上面Event的介紹來看,自動變量一次觸發一個線程,而人工變量一直保持觸發狀態。不過系統並無說若是一個線程很快SetEvent並又ResetEvent的話,Wait的線程會如何。
咱們先看看在MS平臺上該如何解決上面的問題:
最廣泛的方法就是:
1 建立一個Auto的Event,這個Event用來觸發工做線程從任務隊列中獲取任務
2 建立一個Manual的Event,這個Event用來觸發全部工做線程退出
而後利用WaitForMultiObject來等待這兩個Event。
Problem solved!!
三 Linux的實現
Linux上最大的問題是沒有WaitForMultiObject這樣的函數,那麼咱們只能建立一個包含Mutex和Cond的結構體來充當Event
因爲cond的觸發有兩個函數,特性分別是:
- pthread_cond_signal:保證多個線程調用pthread_cond_wait等待的時候只有一個線程可以返回。
- pthread_cond_broadcast:保證多個線程等待的時候,都能返回。
咱們在使用cond實現類型的觸發和等待的時候,代碼常常以下所示:
void Wake(allThreads?)
{
Mutex lock
set condition = 1;
if(allThreads)
pthread_cond_broadcast //觸發全部線程
else
pthread_cond_signal //觸發單個線程
Mutex unlock
}
void Wait()
{
Mutex lock
while(condition != 1)
{
pthread_cond_wait(cond,&mutex); //cond的wait須要傳入一個Mutex作參數,wait函數內部會unlock這個mutex
}
Mutex unlock
}
爲何在Wait的時候須要有一個while循環來檢測condition是否知足條件呢?最主要有兩個緣由:
-
cond自己的機制致使。若是cond觸發(不管是signal仍是broadcast)的時候,沒有線程在wait的話,那麼線程之後wait將沒法捕獲這個觸發。這和Windows的Event大相徑庭。自動變量的Event若是觸發後,即便當時沒有線程在等待,若是之後有線程等待的話,那麼也是能夠返回的。而posix的cond沒有實現這個功能。因此咱們只能本身加condition來作判斷。若是condition爲1,則無需等待。
-
pthread_cond_wait的返回有多是由於信號的緣由致使,這個時候conditon並不知足,因此這裏也須要一個while循環
根據上面的基礎知識,咱們目前已知足的是:
-
cond_signal能而且只能觸發一個線程起來
-
cond_broadcast能觸發全部線程起來
可是問題隨之而來,由於如今只有一個condition,何時置爲零呢?必須知足兩個條件:
解決辦法就是使用waiter計數,最終修改後的代碼以下:
void Wake(allThreads?)
{
Mutex lock
if(condtion == -1 || condition == 1) //爲何要加這樣的判斷?
{
Mutex unlock
return;
}
if(allThreads)
{
set condition = -1; //表示等待多個
pthread_cond_broadcast //觸發全部線程
}
else
{
set condition = 1;
pthread_cond_signal //觸發單個線程
}
Mutex unlock
}
void Wait()
{
Mutex lock
++waiters;
while(condition == 0)
{
pthread_cond_wait(cond,&mutex); //cond的wait須要傳入一個Mutex作參數,wait函數內部會unlock這個mutex
}
if(--waiters == 0)
condition = 0;
Mutex unlock
}
咱們能夠捋一下各類case:
- 只觸發單個線程的話,沒有問題。這個是最簡單的。
- 觸發多個線程的話,若是在觸發後而且在舊的等待線程未所有返還以前,若是又有新線程調用wait的話,其實是不會被阻塞的。
Wake函數前面紅色字體的代碼幹什麼用?
就怕調用者先調用了Wake(true),旋即又調用了Wake(false),致使condtion值混亂。經過判斷condition是否已經觸發,咱們能夠避免出問題。
四:結論
POSIX的cond和mutex聯合使用,總感受效率上會有損失。由於cond_wait返還後仍是須要競爭mutex,實際上Wait函數在用戶空間是串行執行的。
不知道Windows的Event是如何實現的?
上面的代碼應該還不是很完善,有問題再修改,不知道您看出什麼毛病了嗎?