線程同步之mutex和條件變量

併發編程有兩種基本模型,一種是消息傳遞,另一種是共享內存。在分佈式系統中,運行在多臺機器上的多個進程的並行編程只有消息傳遞。在多線程編程中,消息傳遞更容易保證程序的正確性。在用C/C++編寫多線程程序時,我們需要了解共享內存模型下的同步原語。

線程同步的四項原則:

1)首要原則是儘量最低限度地共享對象,減少需要同步的場合。

2)其次是使用高級的併發編程構件,如生產者-消費者隊列。

3)最後不得已必須使用底層的同步原語時,只用非遞歸的互斥器和條件變量,慎用讀寫鎖,不要用信號量。

4)除了使用atomic整數之外,不自己編寫lock-free代碼,也不要用“內核級”同步原語。

平常看書及看代碼,接觸到最多的就是底層同步原語了,linux下的同步原語包括互斥器,條件變量,讀寫鎖,和信號量。可能互斥器和條件變量是大家用的最多的了,C++標準庫中也提供了互斥器,template<classMutex>class lock_guard,是以模板定義的類,lock_guard 對象通常用於管理某個鎖(Lock)對象,因此與 Mutex RAII 相關,方便線程對互斥量上鎖,即在某個 lock_guard 對象的聲明週期內,它所管理的鎖對象會一直保持上鎖狀態;而 lock_guard 的生命週期結束之後,它所管理的鎖對象會被解鎖(注:類似 shared_ptr 等智能指針管理動態分配的內存資源 )。

模板參數 Mutex 代表互斥量類型,例如 std::mutex 類型,是一個基本的 BasicLockable 類型,BasicLockable 類型的對象只需滿足兩種操作,lock 和 unlock,另外還有 Lockable 類型,在 BasicLockable 類型的基礎上新增了 try_lock 操作,因此一個滿足 Lockable 的對象應支持三種操作:lock,unlock 和 try_lock。

在 lock_guard 對象構造時,傳入的 Mutex 對象(即它所管理的 Mutex 對象)會被當前線程鎖住。在lock_guard 對象被析構時,它所管理的 Mutex 對象會自動解鎖,由於不需要程序員手動調用 lock 和 unlock 對 Mutex 進行上鎖和解鎖操作,因此這也是最簡單安全的上鎖和解鎖方式,尤其是在程序拋出異常後先前已被上鎖的 Mutex 對象可以正確進行解鎖操作,極大地簡化了程序員編寫與 Mutex 相關的異常處理代碼。lock_guard 對象並不負責管理 Mutex 對象的生命週期,lock_guard 對象只是簡化了 Mutex 對象的上鎖和解鎖操作,方便線程對互斥量上鎖,即在某個 lock_guard 對象的聲明週期內,它所管理的鎖對象會一直保持上鎖狀態;而 lock_guard 的生命週期結束之後,它所管理的鎖對象會被解鎖。

條件變量是線程可用的另一種同步機制。條件變量給多個線程提供了一個回合的場所。條件變量與互斥器一起使用時,允許線程以無競爭的方式等待特定的條件發生。

條件變量本身是由互斥量保護的。線程在改變條件狀態前必須首先鎖住互斥量,其他線程在獲得互斥量之前不會察覺到這種改變,因爲必須鎖定互斥量以後才能計算條件。

條件變量的使用方式爲,對wait端:

1)必須與mutex一起使用,受mutex保護。

2)在muetx已上鎖的時候才能調用wait().

3判斷布爾條件和wait()放到while循環中,是爲了防止欺騙性喚醒(spurious wakeup)。

實例代碼爲:

struct msg{
   struct msg* m_next;
};
struct msg* workq;
pthread_cond_t qready = PTHREAD_COND_INITIALIZER;
pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER;
void process_msg(void){
   struct mad *mp;
   for(;;){
      pthread_mutex_lock(&qlock);
      while(workq == NULL)
         pthread_cond_wait(&qready,&qlock);
      mp = workq;
      workq = mp->m_next;
      pthread_mutex_unlock(&qlock);
   }
}

對於signal/broadcast端:

1)在signal之前一般要修改布爾表達式。

2)修改布爾表達式之前通常用mutex保護。

3)區分signal與broadcast,broadcast通常用於表明狀態變化,signal通常用於表示資源可用。

即獲取互斥量、改變條件、釋放互斥量並向條件變量發送信號。

實例代碼爲:

void enqueue_msg(struct msg* mp){
   pthread_mutex_lock(&qlock);
   mp->m_next = workq;
   workq = mp;
   pthread_mutex_unlock(&qlock);
   pthread_cond_signal(&qready);
}

互斥器和條件變量構成了多線程編程的全部同步原語,用它們即可完成任何多線程的同步任務,二者不能互相替換。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章