1. 程式人生 > >條件變數用例--解鎖與signal的順序問題

條件變數用例--解鎖與signal的順序問題

         我們知道,當呼叫signal/broadcast喚醒等待條件變數的其他執行緒時,既可以在加鎖的情況下呼叫signal/broadcast,也可以在解鎖的情況下呼叫。

         那麼,到底哪種情況更好呢?man手冊中其實已經給出了答案:

The pthread_cond_broadcast() or pthread_cond_signal() functions may be called by a thread whether or not it currently owns the mutex that threads calling pthread_cond_wait() or  pthread_cond_timedwait() have associated with the condition variable during their waits; however, if predictable scheduling behavior is required, then that mutex shall be locked by the thread calling  pthread_cond_broadcast() or pthread_cond_signal().

         意思就是說,儘管既可以在持有鎖的情況下呼叫signal/broadcast,也可以在解鎖的情況下呼叫,但是如果需要排程行為是可預測的話,則應該在加鎖的情況下呼叫signal/broadcast

一:加鎖時呼叫signal

         某些平臺上,在執行了signal/broadcast之後,為了減少延遲,作業系統會將上下文切換到被喚醒的執行緒。在單核系統上,如果在加鎖的情況下呼叫signal/broadcast,這可能導致不必要的上下文切換。

考慮上圖的場景:T2阻塞在條件變數上,T1在持有鎖的情況下呼叫signal,接著上下文切換到T2,並且T2被喚醒,但是T2在從pthread_cond_wait返回時,需要重新加鎖,然而此時鎖還在T1手中。因此,T2只能繼續阻塞(但是此時是阻塞在鎖上),並且上下文又切換回T1。當T1解鎖時,T2才得以繼續執行。如果是呼叫broadcast喚醒等待條件變數的多個執行緒的話,那這種情形會變得更糟。

為了彌補這種缺陷,一些Pthreads的實現採用了一種叫做waitmorphing的優化措施,也就是當鎖被持有時,直接將執行緒從條件變數佇列移動到互斥鎖佇列,而無需上下文切換。

如果使用的Pthreads實現沒有waitmorphing,我們可能需要在解鎖之後在進行signal/broadcast。解鎖操作並不會導致上下文切換到T2,因為T2是在條件變數上阻塞的。當T2被喚醒時,它發現鎖已經解開了,從而可以對其加鎖。

二:解鎖後呼叫signal

         解鎖後呼叫signal有問題嗎?首先,我們注意到,如果先進行signal/broadcast,則肯定會喚醒一個阻塞在條件變數上的執行緒;然而如果先解鎖,則可能會喚醒一個阻塞在鎖上的執行緒。

         這種情形如何發生的呢?一個執行緒在鎖上阻塞,是因為:

         a:它要檢查條件,並最終會在條件變數上wait;

         b:它要改變條件,並最終通知那些等待條件變數的執行緒;

         在a中,可能會發生喚醒截斷的情況。重新考慮上圖的場景,此時存在第三個執行緒T3阻塞在鎖上。如果T1首先解鎖,則上下文可能會切換到T3。現在T3檢查到條件為真,進行處理,並在T1進行signal/broadcast之前,將條件重置。當T1進行signal/broadcast之後,T2被喚醒,而此時條件已經不再為真了。當然,在設計正確的應用中,這不是問題。因為T2必須考慮偽喚醒的情況。下面的程式碼模擬了這種場景:

#define COND_CHECK(func, cond, retv, errv) \
if ( (cond) ) \
{ \
   fprintf(stderr, "\n[CHECK FAILED at %s:%d]\n| %s(...)=%d (%s)\n\n",\
              __FILE__,__LINE__,func,retv,strerror(errv)); \
   exit(EXIT_FAILURE); \
}
 
#define ErrnoCheck(func,cond,retv)  COND_CHECK(func, cond, retv, errno)
#define PthreadCheck(func,rc) COND_CHECK(func,(rc!=0), rc, rc)
#define FOREVER for(;;) 

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t  cv    = PTHREAD_COND_INITIALIZER;
int predicate = 0;
unsigned long nwakeup   = 0; // number of wakeup
unsigned long nspurious = 0; // number of spurious wakeup

/*****************************************************************************/
/* thread - wait on condvar, do stats and reset the condvar                  */
/*****************************************************************************/
void*
thread(void* ignore)
{
   int rc;

   FOREVER {
      // wait that predicate becomes true 
      //
      rc = pthread_mutex_lock(&mutex);
      PthreadCheck("pthread_mutex_lock", rc);
      while (predicate==0) {
         rc = pthread_cond_wait(&cv, &mutex);
         PthreadCheck("pthread_cond_wait", rc);
         nwakeup++;                     // we've been wakeup
         if (predicate==0) nspurious++; // we got a spurious wakeup
      }

      // reset predicate to false 
      //
      predicate=0;
      rc = pthread_mutex_unlock(&mutex);
      PthreadCheck("pthread_mutex_unlock", rc);
   }

   // never reached 
   //
   pthread_exit(NULL);
}


/*****************************************************************************/
/* signal_thread - set predicate to true and signal the condvar              */
/*****************************************************************************/
void*
signal_thread(void* ignore)
{
   int rc;

   FOREVER {
      // set the predicate to true and wakeup one thread
      //
      rc = pthread_mutex_lock(&mutex);
      PthreadCheck("pthread_mutex_lock", rc);
      predicate=1;
      rc = pthread_mutex_unlock(&mutex); // unlock before signal
      PthreadCheck("pthread_mutex_unlock", rc);
      rc = pthread_cond_signal(&cv);
      PthreadCheck("pthread_cond_signal", rc);
   }

   // never reached 
   //
   pthread_exit(NULL);
}


/*****************************************************************************/
/* main- main thread                                                         */
/*****************************************************************************/

const int NTHREADS = 8; // # threads waiting on the condvar 

int
main()
{
   pthread_t tid[NTHREADS];  // threads waiting on  the condvar
   pthread_t tsig;           // thread that signals the condvar
   int       rc;             // return code

   // create our threads
   //
   for (int i=0; i<NTHREADS; i++) {
      rc = pthread_create(tid+i, NULL, thread, NULL);
      PthreadCheck("pthread_create", rc);
   }
   rc = pthread_create(&tsig, NULL, signal_thread, NULL);
   PthreadCheck("pthread_create", rc);

   // wait 3 sec, print statistics and exit
   //
   sleep(3);
   rc = pthread_mutex_lock(&mutex);
   PthreadCheck("pthread_mutex_lock", rc);
   printf("# wakeup   = %8lu\n# spurious = %8lu (%2.2f%%)\n", 
          nwakeup, nspurious, (float)nspurious/nwakeup*100.0
         );
   rc = pthread_mutex_unlock(&mutex);
   PthreadCheck("pthread_mutex_unlock", rc);

   // that's all, folks!
   //
   return EXIT_SUCCESS;
}

         上面的程式碼中,使用nwakeup記錄pthread_cond_wait被喚醒的次數,用nspurious記錄偽喚醒的次數。執行結果如下:

# wakeup   =   487936
# spurious =   215469 (44.16%)

         可見偽喚醒的佔比要在40%左右。(其實,採用先signal/broadcast,後unlock的寫法,也依然會發生這種情況(親測))

         在b中,會推遲喚醒執行緒T2的時間。第三個執行緒T3阻塞在鎖上,T1解鎖後,T3得以繼續執行。此時,只要T1不被排程,則它沒有機會進行signal/broadcast,因此執行緒T2會一直阻塞。

三:實時的情況

         在實時性的程式中,執行緒的優先順序反映了執行緒deadline的重要性。粗略的說,deadline越重要,則優先順序應該越高。如果無法滿足deadline的要求,則系統可能會失敗、崩潰。

         因此,你肯定希望高優先順序的執行緒能儘可能早的獲取CPU得以執行,然而,有可能會發生優先順序反轉的情況,也就是低優先順序的執行緒阻礙了高優先順序執行緒的執行。比如鎖被低優先順序的執行緒持有,使得高優先順序的執行緒無法加鎖。實際上,只要優先順序反轉的時間是有界且較短的話,這種情況不會造成太大問題。然而當反轉時間變得無界時,這種情況就比較嚴重了,這會導致高優先順序的執行緒無法滿足其deadline。

         當採用實時排程策略時,signal/broadcast會喚醒高優先順序的執行緒。如果多個執行緒具有相同的優先順序,則先在條件變數上阻塞的執行緒會被喚醒。

         線上程進行signal/broadcast之前,也可能會發生優先順序反轉。繼續考慮上圖的場景:T1是個低優先順序(P1)的執行緒,T2是高優先順序(P2)的執行緒,T3的優先順序(P3)介於T1和T2之間:P1 < P3 < P2。

如果T1先進行unlock,則其在unlock和signal/broadcast之間,T1可能被更高優先順序的T3搶佔,從而T1無法喚醒T2,因此低優先順序的T3阻礙了高優先順序的T2的執行,發生了優先順序反轉。

如果T1先進行signal/broadcast,假設鎖使用了優先順序天花板或繼承協議(參考《Programming.With.Posix.Threads》第5.5.5.1節和5.5.5.2節),則可以保證T1在解鎖後,T2會立即被排程。

因此,當持有鎖時進行signal/broadcast更具優勢。基於上面的討論,在實時排程中,先signal/broadcast後unlock是必須的……。

四:陷阱

         如果先解鎖,則可能會導致另一種問題:你必須保證解鎖之後,用於signal/broadcast的條件變數依然有效。比如下面的程式碼:

#define COND_CHECK(func, cond, retv, errv) \
if ( (cond) ) \
{ \
   fprintf(stderr, "\n[CHECK FAILED at %s:%d]\n| %s(...)=%d (%s)\n\n",\
              __FILE__,__LINE__,func,retv,strerror(errv)); \
   exit(EXIT_FAILURE); \
}
 
#define ErrnoCheck(func,cond,retv)  COND_CHECK(func, cond, retv, errno)
#define PthreadCheck(func,rc) COND_CHECK(func,(rc!=0), rc, rc)
#define FOREVER for(;;) 

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t *ptr_cv;
int predicate = 0;
int nthreads; 

/*****************************************************************************/
/* thread - tell the shutdown thread that we're done                        */
/*****************************************************************************/
void*
thread(void* ignore)
{
   int rc;

   // this thread now terminate 
   //
   rc = pthread_mutex_lock(&mutex);
   PthreadCheck("pthread_mutex_lock", rc);

   nthreads--; // we have one thread less in the pool

   // note: we unlock first, and then signal 
   //
   rc = pthread_mutex_unlock(&mutex);
   PthreadCheck("pthread_mutex_unlock", rc);
   rc = pthread_cond_signal(ptr_cv);
   PthreadCheck("pthread_cond_signal", rc);
   
   // Ok, time to retire
   //
   pthread_exit(NULL);
}


/*****************************************************************************/
/* shutdown_thread- wait all threads in the pool to finish and clean-up      */
/* condvar                                                                   */
/*****************************************************************************/
void*
shutdown_thread(void* ignore)
{
   int rc;

   // wait as long as one thread in the pool is running
   //
   rc = pthread_mutex_lock(&mutex);
   PthreadCheck("pthread_mutex_lock", rc);

   while (nthreads>0) {
      rc = pthread_cond_wait(ptr_cv, &mutex);
      PthreadCheck("pthread_cond_wait", rc);
   }

   // all thread stopped running: we can destroy the condvar
   //
   rc = pthread_cond_destroy(ptr_cv);
   PthreadCheck("pthread_cond_destroy", rc);
   free(ptr_cv);

   // unlock mutex, and bye! 
   //
   rc = pthread_mutex_unlock(&mutex);
   PthreadCheck("pthread_mutex_unlock", rc);
   pthread_exit(NULL);
}


/*****************************************************************************/
/* main- main thread                                                         */
/*****************************************************************************/

const int NTHREADS = 8; // # threads in the pool

int
main()
{
   pthread_t     pool[NTHREADS]; // threads pool
   pthread_t     tshd;           // shutdown thread
   unsigned long count=0;        // counter
   int           rc;             // return code

   FOREVER {

      // initialize condvar 
      //
      nthreads=NTHREADS;
      ptr_cv = (pthread_cond_t*) malloc(sizeof(*ptr_cv));
      ErrnoCheck("malloc", (ptr_cv==NULL), 0);
      rc = pthread_cond_init(ptr_cv, NULL);
      PthreadCheck("pthread_cond_init", rc);

      // create shutdown thread
      //
      rc = pthread_create(&tshd, NULL, shutdown_thread, NULL);
      PthreadCheck("pthread_create", rc);

      // create threads pool
      //
      for (int i=0; i<NTHREADS; i++) {
         rc = pthread_create(pool+i, NULL, thread, NULL);
         PthreadCheck("pthread_create", rc);
         rc = pthread_detach(pool[i]);
         PthreadCheck("pthread_detach", rc);
      }

      // wait shutdown thread completion
      //
      rc = pthread_join(tshd, NULL); 
      PthreadCheck("pthread_join", rc);

      // great... one more round
      //
      ++count;
      printf("%lu\n", count); 
   }

   // should be never reached
   //
   return EXIT_SUCCESS;
}

         上面的程式碼在執行時,會發生Segmentationfault。

五:結論

         我個人傾向於,在持有鎖的情況下進行signal/broadcast。首先,這樣做可以避免隱蔽的bug;然後,在使用了wait morphing優化的Pthreads實現中,這樣做幾乎沒有效能損耗;其次,我認為只有在明確表明效能可以得到顯著提升時,才有必要先unlock,後signal/broadcast,優化那些並非導致效能瓶頸的點,是沒有必要的。

原文:

http://www.domaigne.com/blog/computing/condvars-signal-with-mutex-locked-or-not/

相關推薦

條件變數--signal順序問題

         我們知道,當呼叫signal/broadcast喚醒等待條件變數的其他執行緒時,既可以在加鎖的情況下呼叫signal/broadcast,也可以在解鎖的情況下呼叫。          那麼,到底哪種情況更好呢?man手冊中其實已經給出了答案: The pt

Oracle11G的、卸載以及基礎操作

edi document ces event tin log 列操作 權限 若有 Oracle用戶解鎖 【以下操作,必須以超級管理員身份登錄,才能修改】oracle安裝後,會默認生成很多個用戶 以超級管理員身份登錄,請註意,其中的空格符:【 sys是一個超級管理員,有最大的

Android 監聽屏幕屏&

clas 開始 ets adc spa contex screen context scree 在做視頻播放器的時候,遇到一個問題,在用戶播放視頻然後鎖屏之後,視頻播放器仍然在繼續播放,遇到類似手機系統狀態改變的問題的時候,首先想到了廣播,下面做個總結: public

測試管理工具禪道

應該 測試工程 失敗 補充 一個 根據 情況 經驗 見證 測試用例: 1.定義:設計一種情況,軟件在這種情況下,能夠正常運行並且達到期望執行的結果;如果程序在這種情況下不能正常運行,而且這種問題會重復發生,那個可能是一個缺陷; 2.來源:需求分析、評審(需求以及需求發掘)

3.條件變數(1.互斥;2,讀寫

先介紹幾個api: pthread_cond_t表示多執行緒的條件變數,用於控制執行緒等待和就緒的條件。 一:條件變數的初始化: 條件變數和互斥鎖一樣,都有靜態動態兩種建立方式, 靜態方式使用PTHREAD_COND_INITIALIZER常量初始化。 pthread_cond_

測試的特點原則

測試用例的特點 1、正確性:測試用例最好是要求輸入使用者實際資料已驗證系統是否滿足需求規格說明書的需求,並且測試用例中的測試的應保證至少覆蓋需求規格說明書中的各項功能。 2、完整性:一些基本功能,如有遺漏,那是不可原諒的。 3、準確:按測試用例輸入實施測試後,要能根據測試用

Linux使用互斥條件變數實現讀寫(寫優先)

(1)只要沒有執行緒持有某個給定的讀寫鎖用於寫,那麼任意數目的執行緒可以持有該讀寫鎖用於讀(2)僅當沒有執行緒持有某個讀寫鎖用於讀或用於寫時,才能分配該讀寫鎖用於寫換一種說法就是,只要沒有執行緒在修改(寫)某個給定的資料,那麼任意數目的執行緒都可以擁有該資料的訪問權(讀)。僅

MySQL

1.檢視正在被鎖定的表 show OPEN TABLES where In_use > 0; 2.查看錶狀態 show status like 'table%'; Table_locks_immediate 指的是能夠立即獲得表級鎖的次數 T

測試編寫要素模板

測試用例設計是測試工作的核心任務之一,也是工作量最大的任務之一。一般來說,編寫測試用例所涉及的內容或者要素以及樣式均大同小異,一般都包含主題、前置條件、執行步驟、期望結果等。測試用例可以用資料庫、Word、Excel、xml等格式進行儲存和管理。 1.編寫測試用例要素 一般

Oracle 數據庫登錄、、改密碼、創建戶授權操作

登錄 分享圖片 管理 ima ont 找到 免密 nbsp res 一、數據庫登錄1、常用賬戶: 管理員: sys主要練習操作用戶: scott2、測試環境是否配置成功: 1、命令窗口 win+R -> cmd(以管理員身份運行) - > sqlplus -&g

Selenium(八)測試的設計模塊化

xxx col 窗口 登錄 打開 urn selenium def firefox 一.設計測試用例 1.分析我之前寫的登錄腳本: from selenium import webdriver import time from selenium.webdrive

linux下互斥條件變數來實現讀寫

以下內容來源自UNP卷二的第八章 讀寫鎖的概念( the conception of read-write lock ) (1)只要沒有執行緒持有某個給定的讀寫鎖用於寫,那麼任意數目的執行緒可以持有該執行緒用於讀 (2)僅當沒有執行緒持有某個給定的讀寫鎖用於讀或用於寫,才能分配該

執行緒、互斥條件變數例項理解

互斥鎖: 初始化程序鎖: int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr); 其中 mutex 為鎖號 attr為屬性 摧毀互

通過互斥條件變數實現佇列的無sleep生產消費

昨天轉了一篇文章將條件變數與互斥鎖的結合使用,今天給大家摳出曾經學到的一段高效的處理訊息的demo,通過互斥鎖實現安全佇列,通過條件變數實現佇列的工作與休息。 目錄結構: 簡單介紹: SignalThread是封裝好的執行緒類,我們所做的核心工作都是在這裡完成,其他類也是服務於這裡;

互斥量、條件變數pthread_cond_wait()函式的使用,詳(二)

1.Linux“執行緒”      程序與執行緒之間是有區別的,不過linux核心只提供了輕量程序的支援,未實現執行緒模型。Linux是一種“多程序單執行緒”的作業系統。Linux本身只有程序的概念,而其所謂的“執行緒”本質上在核心裡仍然是程序。      大家知道,

【Linux C 多執行緒程式設計】互斥條件變數

一、互斥鎖互斥量從本質上說就是一把鎖, 提供對共享資源的保護訪問。  1. 初始化:  在Linux下, 執行緒的互斥量資料型別是pthread_mutex_t. 在使用前, 要對它進行初始化:  對於靜態分配的互斥量, 可以把它設定為PTHREAD_MUTEX_INITIA

Linux多執行緒學習(2)--執行緒的同步互斥及死問題(互斥量和條件變數)

Linux多執行緒學習總結 一.互斥量 1.名詞理解 2.什麼是互斥量(mutex) 3.互斥量的介面 3.1 初始化訊號量 3.2 銷燬訊號量 3.3 互斥量加鎖和解鎖

多執行緒互斥條件變數實現生產者和消費者-------迴圈任務佇列

互斥鎖與條件變數簡介 在多執行緒的環境中,全域性變數會被各執行緒共享,因此在操作全域性變數的時候需要採用鎖機制,在linux裡最常用的鎖就是互斥鎖,互斥鎖使用方法如下 <pre name="code" class="cpp">//執行緒A pthread_mut

linux網路程式設計之posix 執行緒(四):posix 條件變數互斥 示例生產者--消費者問題

#include <unistd.h>#include <sys/types.h>#include <pthread.h>#include <semaphore.h>#include <stdlib.h>#include <stdio.h>

速達的時序圖

速達軟件 部分 訂單 軟件 log 技術 1-1 mage width 上圖這是我們速達軟件用戶之間的關系做出的用例圖,它們的關系就是註冊登錄選擇菜單,並且分為完成訂單和未完成兩部分進行。為完成就是結算,以完成就是終結。 這個是時序圖,它的構成基本就是整個軟件互相之間