隨筆之POSIX cond和Windows同步對象Event的討論

 
一 原因
最近在實現一個線程池的時候,須要用到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,何時置爲零呢?必須知足兩個條件:
  • 對於觸發單個線程的來講,由於只有一個啓動,因此當它啓動後,須要把condtion置爲零,表示本身已經消費了這個條件。
  • 對於全部啓動的來講,只有最後一個線程退出等待的時候須要把conditon置爲零,表示全部人都消費了這個條件。
解決辦法就是使用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是如何實現的?
上面的代碼應該還不是很完善,有問題再修改,不知道您看出什麼毛病了嗎?
相關文章
相關標籤/搜索