1. 程式人生 > >C++ BOOST庫 條件變數[多執行緒通訊]機制 [大三四八九月實習]

C++ BOOST庫 條件變數[多執行緒通訊]機制 [大三四八九月實習]

1相關理念

(1)類名

條件變數和互斥變數都是boost庫中被封裝的類。

(2)條件變數

條件變數是thread庫提供的一種等待執行緒同步的機制,可實現執行緒間的通訊,它必須與互斥量配合使用,等待另一個執行緒中某個事件發生後本執行緒才能繼續執行。

(3)互斥變數

互斥量是一種用於多執行緒程式設計的手段,它可以在多執行緒程式設計中防止多個執行緒同時操作共享資源[或稱為臨界區 ]。思想為:在每個執行緒開始的第一條語句使用獲取互斥變數“鎖有權”的語句,一旦一個執行緒[執行緒1]鎖住了互斥量,那麼其它執行緒只有等待執行緒1解鎖互斥量後且另一執行緒[執行緒2]又獲取到互斥變數的“鎖有權”後才能執行這個執行緒[

執行緒2]後面的程式碼。

(4)互斥量有可能的缺點

[1]互斥變數的使用讓執行緒整個整個的執行。

[2]如果“同時”建立多執行緒,則首先獲取到互斥變數“鎖有權”的執行緒是不定的。

(5)條件變數的簡單應用場景

有時候我們需要先執行某個執行緒一會兒後才能執行另一個執行緒,或者是需要先執行某個執行緒[執行緒1]一會兒後必須執行另一個執行緒[執行緒2]後執行緒1才能夠繼續執行。C++ boost庫對條件變數的封裝 就是來解決這些問題的。

2 條件變數的使用機制總結

C++boost 的thread庫提供兩種條件變數物件condition_variable及condition_variable_any,由於後者能夠適用於更廣泛的互斥量型別,所以一般用condition_variable_any,其類摘要如下:

class condition_variable_any
{
public:
	void notify_one();
	void notify_all();
	
	template <typename  lock_type>
	void wait(lock_type  &lock);

	template<typename lock_type, typename  predicate_type>
	void wait(lock_type  &lock,predicate_type  predicate);

	template<typename lock_type, typename  duration_type>
	bool timed_wait(lock_type  &lock, duration_type  const&  rel_time);
}

條件變數condition_variable_any中只封裝了]notify_one(),notify_all()和wait()系列兩類函式。

2.1參考書對條件變數的描述

擁有條件變數的執行緒先鎖定互斥量,然後迴圈檢查某個條件,如果條件不滿足,那麼就呼叫成員函式wait()等至條件滿足。而其它執行緒處理條件變數要求的條件,當條件滿足時呼叫它的成員函式notify_one()notify_all(),以通知所有正在等待條件變數的執行緒停止等待繼續執行。

其實這一小段話就將condition_variable_any類摘要的使用機制給描述了,但是不得不承認我並沒有讀懂,所以還得繼續寫程式驗證一下條件變數到底封裝了怎麼一個機制。

2.2實踐總結筆記

(1)簡單使用條件變數步驟

[1]定義生存期和作用域能夠應用於多執行緒中的條件變數物件和互斥變數物件。條件變數物件的個數據程式需求制定。

condition_variable_any  con_var_1;

condition  variable_anu  con_var_2;

mutex  mu;

[2]每個執行緒對應函式的第一條語句使用鎖定互斥量的語句,最後一條語句使用解鎖互斥量的語句。【如果不使用條件變數來實現執行緒通訊,則如此就能夠使多執行緒中單個執行緒一個接連一個的執行,執行時間和場合的不同,各執行緒執行的順序也不同】

void  thread_fun_1()

{

    mu.lock();

……

mu.unlock();

}

void  thread_fun_2()

{

mu.lock();

……

mu.unlock();

}

[3]thread_fun_1()輸出”123456789”字串的奇數數字,thread_fun_2()輸出字串的偶數數字,並且用條件變數和多執行緒來實現

void  thread_fun_1( const  string  &str )

{

mu.lock();

while ( i < str.length() )

{

         while(  !(i % 2) )

         {

                    con_var_1.wait( mu );

         }

         if  ( i < str.length() )

         {

                     cout << "p1:" <<  str[i] <<"\n";

         }

         ++i;

//i++後一定是奇數,但是如果是此執行緒先執行,則第一次通知print_2.wait()時它都不曾等待過

         con_var_2.notify_one();

}

mu.unlock();

}

void  thread_fun_2( const  string  &str )

{

mu.lock();

while ( i < str.length() )

{

         while(  i % 2 )

         {

                    con_var_2.wait( mu );

         }

         if  ( i < str.length() )

         {

                     cout << "p2:" <<  str[i] <<"\n";

         }

         ++i;

//i++後一定是奇數,但是如果是此執行緒先執行,則第一次通知print_2.wait()時它都不曾等待過

         con_var_1.notify_one();

}

mu.unlock();

}

其中i可以是兩個執行緒函式都能夠訪問的全域性變數。

[4]在主程式中建立兩個執行緒來跑這兩個執行緒函式。

thread  th_1(thread_fun_1, “123456789”);

thread  th_2(thread_fun_2, ”123456789”);

可以使用getchar()函式讓執行視窗停留,程式執行效果為目標期待的樣子【前提是知道boost庫的使用需要包含的標頭檔案及開發環境的配置】:

Figure1:條件變數應用於多執行緒執行結果

(2)程式執行分析[wait與notify]

當不懂得一個程式執行的結果時,那就老老實實從最基本的機制用自己的腦袋將有必要的那部分程式執行一遍。我的笨方法。

  • [1]兩個執行緒不管哪一個先獲取到互斥變數mu的“鎖有權”,另一個執行緒的執行就會被阻塞。如執行緒th_2先獲取到互斥變數mu的“鎖有權”,那麼就進入thread_fun_2內,此時th_1不擁有互斥變數的“鎖有權”就會被其執行緒函式內的mu.lock()語句阻塞。
  • [2]thread_fun_2列印一次字元到螢幕之上後就會進入以 i%2為條件的while迴圈中執行”con_var_2.wait( mu);”語句。根據條件變數wait()函式的功能,th_2執行緒就會開始睡眠[就停留在”con_var_2.wait( mu );”語句處],同時wait(mu)函式還將unlock互斥量mu,在時刻mu.lock(mu)[等待鎖定互斥量mu]的執行緒th_1立馬獲得互斥量的“鎖有權”,然後開始執行執行緒th_1對應函式thread_fun_1內的內容[如果th_1內沒有使用wait程式碼,那麼此次執行緒th_1將會執行完畢,不是con_var_2.notify_one()函式一執行和th_2con_var_2.wait()完成通訊之後就會立即返回到th_2對應函式thread_fun_2中繼續執行,而是會將th_1執行緒執行完之後再回到th_2(thread_fun_2)con_var_2.wait()語句處 ,此時con_var_2.wait()函式返回,然後wait函式將再lock互斥變數mu。然後再判斷while中的條件,如果條件為假則執行下一條語句,如果while中的條件依然為真,那麼重新呼叫con_var_2.wait()函式繼續等待]
  • [3]th_1d對應函式列印一個偶數數字到螢幕之後,進入while之中執行con_var_1.wait( mu )語句,由於此時執行緒th_2”con_var_2.wait( mu );”已經收到過”con_var_1.notify_one();”的通訊,所以th_2繼續lock  mu,然後執行下一段程式碼。在++i後,再通知th_1中的con_var_1.wait(mu )函式,等th_2再次發生等待時,th_1中的wait函式屬性,重新鎖定互斥變數mu,繼續執行。如此反覆執行程式碼,直到兩個執行緒都執行完畢為止。
  • [4]不過也發現了一個問題:兩個執行緒初次使用notify_one()函式時,對方執行緒內都還沒有使用過wait函式,那麼notify_one()函式初次使用時是個什麼狀況?或許condition_variable_any在封裝了處理這個問題的程式碼。I do not know now

(3)條件變數封裝機理[wait與notify]

其實這個機制在程式執行分析時已經完全的體現了。不過看起來挺長的樣子,語言描述也不流暢。估計我今後也懶得看,還是整理一個比較簡潔的版本來概括一下條件變數使用的機理。

  • [1]條件變數類的使用體現在waitnotify_one兩個函式之上
  • [2]當在一個執行緒中用一個條件變數物件1呼叫wait函式時,它將起到暫時unlock多執行緒間供用的互斥變數而讓本執行緒陷入等待之中。【由於對互斥變數的unlock,其它的執行緒會鎖定互斥變數】,等其它執行緒執行完畢,再用條件變數物件1呼叫notify_one函式時,wait函式就會甦醒,並重新lock互斥變數,繼續執行本執行緒。

總而言之,條件變數是一種邏輯控制,就是給咱提供了兩個函式:wait函式用於發出訊號並等待[還有unlock/lock機制]notify_one就是通知wait函式不用等了醒來返回吧。當在每個執行緒中都使用條件變數提供的這種機制時,邏輯上就比較複雜了,但是對於cpu來說很好分清,只要根據執行結果不斷的除錯,總會調到自己期望的邏輯效果。

3使用條件變數的基礎

(1)純粹的多執行緒執行

如果在程式中建立了多個執行緒,不使用互斥變數來約束各執行緒的執行順序,那麼當某個執行緒被定義時它就開始執行,在多執行緒的情況下,各執行緒以極短的時間交替執行著,可以認為在“同一時間”他們都在執行。

(2)使用互斥變數約束執行緒的執行

對於使用相同互斥變數的多個執行緒來說,只有獲取到互斥量“鎖有權”的執行緒才能執行,否則就會被阻塞直到獲取到了互斥量的“鎖有權”。所以,執行緒是被整個整個依次執行完畢的。而且很有可能是這樣:不同時間的執行對應著不同次序的執行,獲取互斥變數的順序不同造成的。

(3)規定執行緒的執行順序

在互斥變數的基礎之上,使用條件變數就是能實現標題的內容。執行緒既不是“同時”執行也不是會因為執行環境的不同而造成各執行緒執行順序的不同。

1)~(3)是循序漸進的一個過程。

Thread Note Over。