怎么理解C++11中的線程及鎖和條件變量

今天就跟大家聊聊有關(guān)怎么理解C++11 中的線程及鎖和條件變量,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。

創(chuàng)新互聯(lián)堅(jiān)持“要么做到,要么別承諾”的工作理念,服務(wù)領(lǐng)域包括:網(wǎng)站設(shè)計(jì)制作、成都網(wǎng)站制作、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣等服務(wù),滿足客戶于互聯(lián)網(wǎng)時(shí)代的周口網(wǎng)站設(shè)計(jì)、移動(dòng)媒體設(shè)計(jì)的需求,幫助企業(yè)找到有效的互聯(lián)網(wǎng)解決方案。努力成為您成熟可靠的網(wǎng)絡(luò)建設(shè)合作伙伴!

線程

類std::thread代表一個(gè)可執(zhí)行線程,使用時(shí)必須包含頭文件<thread>。std::thread可以和普通函數(shù),匿名函數(shù)和仿函數(shù)(一個(gè)實(shí)現(xiàn)了operator()函數(shù)的類)一同使用。另外,它允許向線程函數(shù)傳遞任意數(shù)量的參數(shù)。

#include <thread>   void func() {    // do some work }   int main() {    std::thread t(func);    t.join();      return 0; }

上例中,t 是一個(gè)線程對(duì)象,函數(shù)func()運(yùn)行于該線程中。對(duì)join()函數(shù)的調(diào)用將使調(diào)用線程(本例是指主線程)一直處于阻塞狀態(tài),直到正在執(zhí)行的線程t執(zhí)行結(jié)束。如果線程函數(shù)返回某個(gè)值,該值也將被忽略。不過(guò),該函數(shù)可以接收任意數(shù)量的參數(shù)。

void func(int i, double d, const std::string& s) {     std::cout << i << ", " << d << ", " << s << std::endl; }   int main() {    std::thread t(func, 1, 12.50, "sample");    t.join();      return 0; }

盡管可以向線程函數(shù)傳遞任意數(shù)量的參數(shù),但是所有的參數(shù)應(yīng)當(dāng)按值傳遞。如果需要將參數(shù)按引用傳遞,那要向下例所示那樣,必須將參數(shù)用std::ref 或者std::cref進(jìn)行封裝。

void func(int& a) {    a++; }   int main() {    int a = 42;    std::thread t(func, std::ref(a));    t.join();      std::cout << a << std::endl;      return 0; }

該程序打印結(jié)果為43,但是如果不用std::ref把參數(shù)a進(jìn)行封裝的話,輸出結(jié)果將為42.

除了join方法外,該線程類還提供了另外兩個(gè)方法:

swap:交換兩個(gè)線程對(duì)象的底層句柄。

Detach: 允許執(zhí)行該方法的線程脫離其線程對(duì)象而繼續(xù)獨(dú)立執(zhí)行。脫離后的線程不再是可結(jié)合線程(你不能等待它們執(zhí)行結(jié)束)。

int main() {     std::thread t(funct);     t.detach();       return 0; }

有一點(diǎn)非常重要,如果線程函數(shù)拋出異常,使用常規(guī)的try-catch語(yǔ)句是捕獲不到該異常的。換句話說(shuō),以下的做法是不可行的:

try {     std::thread t1(func);     std::thread t2(func);       t1.join();     t2.join(); } catch(const std::exception& ex) {     std::cout << ex.what() << std::endl; }

要在線程間傳遞異常,你需要在線程函數(shù)中捕獲他們,將其存儲(chǔ)在合適的地方,比便于另外的線程可以隨后獲取到這些異常。

std::mutex                       g_mutex; std::vector<std::exception_ptr>  g_exceptions;   void throw_function() {    throw std::exception("something wrong happened"); }   void func() {    try    {       throw_function();    }    catch(...)    {       std::lock_guard<std::mutex> lock(g_mutex);       g_exceptions.push_back(std::current_exception());    } }   int main() {    g_exceptions.clear();      std::thread t(func);    t.join();      for(auto& e : g_exceptions)    {       try       {          if(e != nullptr)          {             std::rethrow_exception(e);          }       }       catch(const std::exception& e)       {          std::cout << e.what() << std::endl;       }    }      return 0; }

想要知道更多的關(guān)于捕獲和傳遞異常的知識(shí),可以閱讀這兩本書在主線程中處理輔助線程拋出的C++異常怎樣在線程間傳遞異常。

在深入學(xué)習(xí)之前,有一點(diǎn)需要注意 &lt;thread&gt;頭文件在命名空間std::this_thread中提供了一些幫助函數(shù):

  • get_id: 返回當(dāng)前線程的id.

  • yield:在處于等待狀態(tài)時(shí),可以讓調(diào)度器先運(yùn)行其他可用的線程。

  • sleep_for:阻塞當(dāng)前線程,時(shí)間不少于其參數(shù)指定的時(shí)間。

  • sleep_util:在參數(shù)指定的時(shí)間到達(dá)之前,使當(dāng)前線程一直處于阻塞狀態(tài)。

在上面的例子中,我需要對(duì)vector  g_exceptions進(jìn)行同步訪問(wèn),以確保在同一時(shí)間只能有一個(gè)線程向其中添加新元素。為此,我使用了互斥量,并對(duì)該互斥進(jìn)行加鎖?;コ饬渴且粋€(gè)核心 同步原語(yǔ),C++ 11的<mutex>頭文件里包含了四種不同的互斥量。

  • Mutex: 提供了核心函數(shù) lock() 和 unlock(),以及非阻塞方法的try_lock()方法,一旦互斥量不可用,該方法會(huì)立即返回。

  • Recursive_mutex:允許在同一個(gè)線程中對(duì)一個(gè)互斥量的多次請(qǐng)求。

  • Timed_mutex:同上面的mutex類似,但它還有另外兩個(gè)方法 try_lock_for() 和 try_lock_until(),分別用于在某個(gè)時(shí)間段里或者某個(gè)時(shí)刻到達(dá)之間獲取該互斥量。

  • Recursive_timed_mutex: 結(jié)合了timed_mutex 和recuseive_mutex的使用。

下面是一個(gè)使用了std::mutex的例子(注意前面提到過(guò)的幫助函數(shù)get_id()和sleep_for()的用法)。

#include <iostream> #include <thread> #include <mutex> #include <chrono>   std::mutex g_lock;   void func() {     g_lock.lock();       std::cout << "entered thread " << std::this_thread::get_id() << std::endl;     std::this_thread::sleep_for(std::chrono::seconds(rand() % 10));     std::cout << "leaving thread " << std::this_thread::get_id() << std::endl;       g_lock.unlock(); }   int main() {     srand((unsigned int)time(0));       std::thread t1(func);     std::thread t2(func);     std::thread t3(func);       t1.join();     t2.join();     t3.join();       return 0; }

輸出結(jié)果如下所示:

entered thread 10144 leaving thread 10144 entered thread 4188 leaving thread 4188 entered thread 3424 leaving thread 3424

lock()和unlock()這兩個(gè)方法應(yīng)該一目了然,***個(gè)方法用來(lái)對(duì)互斥量加鎖,如果互斥量不可用,便處于阻塞狀態(tài)。后者則用來(lái)對(duì)互斥量解鎖。

下面這個(gè)例子展示了一個(gè)簡(jiǎn)單的線程安全容器(內(nèi)部使用std::vector).這個(gè)容器帶有添加單個(gè)元素的add()方法和添加多個(gè)元素的addrange()方法,addrange()方法內(nèi)部?jī)H僅調(diào)用了add()方法。

注意:就像下面的評(píng)論里所指出的一樣,由于某些原因,包括使用了va_args,這不是一個(gè)標(biāo)準(zhǔn)的線程安全容器。而且,dump()方法也不是容器 的方法,從真正的實(shí)現(xiàn)上來(lái)說(shuō),它只是一個(gè)幫助(獨(dú)立的)函數(shù)。這個(gè)例子僅僅用來(lái)告訴大家一些有關(guān)互斥量的概念,而不是實(shí)現(xiàn)一個(gè)完全成熟的,無(wú)任何錯(cuò)誤的線 程安全容器。

template <typename T> class container {               std::mutex _lock;     std::vector<T> _elements; public:     void add(T element)     {         _lock.lock();         _elements.push_back(element);         _lock.unlock();     }       void addrange(int num, ...)     {         va_list arguments;           va_start(arguments, num);           for (int i = 0; i < num; i++)         {             _lock.lock();             add(va_arg(arguments, T));             _lock.unlock();         }           va_end(arguments);     }       void dump()     {         _lock.lock();         for(auto e : _elements)             std::cout << e << std::endl;         _lock.unlock();     } };   void func(container<int>& cont) {     cont.addrange(3, rand(), rand(), rand()); }   int main() {     srand((unsigned int)time(0));       container<int> cont;       std::thread t1(func, std::ref(cont));     std::thread t2(func, std::ref(cont));     std::thread t3(func, std::ref(cont));       t1.join();     t2.join();     t3.join();       cont.dump();       return 0; }

運(yùn)行該程序時(shí),會(huì)進(jìn)入死鎖狀態(tài)。原因是該容器試圖多次去獲取同一個(gè)互斥量,卻一直沒有釋放它,這樣是不可行的。

在這里,使用std::recursive_mutex就可以很好地解決這個(gè)問(wèn)題,它允許同一個(gè)線程多次獲取同一個(gè)互斥量,可獲取的互斥量的***次數(shù)并沒有具體說(shuō)明。但是一旦超過(guò)***次數(shù),再對(duì)lock進(jìn)行調(diào)用就會(huì)拋出std::system_error錯(cuò)誤異常。

要想修改上述代碼中的問(wèn)題(除了修改addrange()方法的實(shí)現(xiàn),使它不去調(diào)用lock()和unlock()),還可以將互斥量std::mutex改為std::recursive_mutex

template <typename T> class container {               std::mutex _lock;     std::vector<T> _elements; public:     void add(T element)     {         _lock.lock();         _elements.push_back(element);         _lock.unlock();     }       void addrange(int num, ...)     {         va_list arguments;           va_start(arguments, num);           for (int i = 0; i < num; i++)         {             _lock.lock();             add(va_arg(arguments, T));             _lock.unlock();         }           va_end(arguments);     }       void dump()     {         _lock.lock();         for(auto e : _elements)             std::cout << e << std::endl;         _lock.unlock();     } };   void func(container<int>& cont) {     cont.addrange(3, rand(), rand(), rand()); }   int main() {     srand((unsigned int)time(0));       container<int> cont;       std::thread t1(func, std::ref(cont));     std::thread t2(func, std::ref(cont));     std::thread t3(func, std::ref(cont));       t1.join();     t2.join();     t3.join();       cont.dump();       return 0; }

修改后,就會(huì)得到下面的輸出結(jié)果。

6334 18467 41 6334 18467 41 6334 18467 41

聰明的讀者會(huì)注意到每次調(diào)用func()都會(huì)產(chǎn)生相同的數(shù)字序列。這是因?yàn)榉N子數(shù)是線程本地化的,僅僅在主線程中調(diào)用了srand()對(duì)種子進(jìn)行了初始化,在其他工作線程中并沒用進(jìn)行初始化,所以每次都得到相同的數(shù)字序列。

顯式的加鎖和解鎖會(huì)導(dǎo)致一些問(wèn)題,比如忘記解鎖或者請(qǐng)求加鎖的順序不正確,進(jìn)而產(chǎn)生死鎖。該標(biāo)準(zhǔn)提供了一些類和函數(shù)幫助解決此類問(wèn)題。這些封裝類保證了在RAII風(fēng)格上互斥量使用的一致性,可以在給定的代碼范圍內(nèi)自動(dòng)加鎖和解鎖。封裝類包括:

Lock_guard:在構(gòu)造對(duì)象時(shí),它試圖去獲取互斥量的所有權(quán)(通過(guò)調(diào)用lock()),在析構(gòu)對(duì)象時(shí),自動(dòng)釋放互斥量(通過(guò)調(diào)用unlock()).這是一個(gè)***的類。

Unique_lock:這個(gè)一通用的互斥量封裝類,不同于lock_guard,它還支持延遲加鎖,時(shí)間加鎖和遞歸加鎖以及鎖所有權(quán)的轉(zhuǎn)移和條件變量的使用。這也是一個(gè)***的類,但它是可移動(dòng)類。

有了這些封裝類,我們可以像下面這樣改寫容器類:

template <typename T> class container {     std::recursive_mutex _lock;     std::vector<T> _elements; public:     void add(T element)     {         std::lock_guard<std::recursive_mutex> locker(_lock);         _elements.push_back(element);     }       void addrange(int num, ...)     {         va_list arguments;           va_start(arguments, num);           for (int i = 0; i < num; i++)         {             std::lock_guard<std::recursive_mutex> locker(_lock);             add(va_arg(arguments, T));         }           va_end(arguments);     }       void dump()     {         std::lock_guard<std::recursive_mutex> locker(_lock);         for(auto e : _elements)             std::cout << e << std::endl;     } };

有人也許會(huì)問(wèn),既然dump()方法并沒有對(duì)容器的狀態(tài)做任何修改,是不是應(yīng)該定義為const方法呢?但是你如果將它定義為const,編譯器會(huì)報(bào)出下面的錯(cuò)誤:

&lsquo;std::lock_guard<_Mutex>::lock_guard(_Mutex  &)&rsquo; : cannot convert parameter 1 from &lsquo;const std::recursive_mutex&rsquo;  to &lsquo;std::recursive_mutex &&rsquo;

一個(gè)互斥量(不管使用的哪一種實(shí)現(xiàn))必須要獲取和釋放,這就意味著要調(diào)用非const的lock()和unlock()方法。所以從邏輯上來(lái) 講,lock_guard的參數(shù)不能使const(因?yàn)槿绻摲椒閏onst,互斥量也必需是const).解決這個(gè)問(wèn)題的辦法就是將互斥量定義為可變 的mutable,Mutable允許在常函數(shù)中修改狀態(tài)。

不過(guò),這種方法只能用于隱藏或者元狀態(tài)(就像對(duì)計(jì)算結(jié)果或查詢的數(shù)據(jù)進(jìn)行緩存,以便下次調(diào)用時(shí)可以直接使用,不需要進(jìn)行多次計(jì)算和查詢。再或者,對(duì)在一個(gè)對(duì)象的實(shí)際狀態(tài)起輔助作用的互斥量進(jìn)行位的修改)。

template <typename T> class container {    mutable std::recursive_mutex _lock;    std::vector<T> _elements; public:    void dump() const    {       std::lock_guard<std::recursive_mutex> locker(_lock);       for(auto e : _elements)          std::cout << e << std::endl;    } };

這些封裝類的構(gòu)造函數(shù)可以重載,接受一個(gè)參數(shù)用來(lái)指明加鎖策略??捎玫牟呗匀缦拢?/p>

  • defer_lock of type defer_lock_t:不獲取互斥量的擁有權(quán)

  • try_to_lock of type try_to_lock_t:在不阻塞的情況下試圖獲取互斥量的擁有權(quán)

  • adopte_lock of type adopt_lock_t:假設(shè)調(diào)用線程已經(jīng)擁有互斥量的所有權(quán) 

這些策略的聲明如下:

struct defer_lock_t { }; struct try_to_lock_t { }; struct adopt_lock_t { };   constexpr std::defer_lock_t defer_lock = std::defer_lock_t(); constexpr std::try_to_lock_t try_to_lock = std::try_to_lock_t(); constexpr std::adopt_lock_t adopt_lock = std::adopt_lock_t();

除了這些互斥量的封裝類,該標(biāo)準(zhǔn)還提供了兩個(gè)方法,用于對(duì)一個(gè)或多個(gè)互斥量進(jìn)行加鎖。

  • lock:使用一種可以避免死鎖的算法對(duì)互斥量加鎖(通過(guò)調(diào)用lock(),try_lock()和unlock()).

  • try_lock():按照互斥量被指定的順序,試著通過(guò)調(diào)用try_lock()來(lái)對(duì)多個(gè)互斥量加鎖。

這是一個(gè)發(fā)生死鎖的例子:有一個(gè)用來(lái)存儲(chǔ)元素的容器和一個(gè)函數(shù)exchange(),該函數(shù)用來(lái)交換兩個(gè)容器中的元素。要成為線程安全函數(shù),該函數(shù)通過(guò)獲取每個(gè)容器的互斥量,來(lái)對(duì)兩個(gè)容器的訪問(wèn)進(jìn)行同步操作。

template <typename T> class container { public:     std::mutex _lock;     std::set<T> _elements;       void add(T element)     {         _elements.insert(element);     }       void remove(T element)     {         _elements.erase(element);     } };   void exchange(container<int>& cont1, container<int>& cont2, int value) {     cont1._lock.lock();     std::this_thread::sleep_for(std::chrono::seconds(1)); // <-- forces context switch to simulate the deadlock     cont2._lock.lock();          cont1.remove(value);     cont2.add(value);       cont1._lock.unlock();     cont2._lock.unlock(); }

假設(shè)這個(gè)函數(shù)是由兩個(gè)不同的線程進(jìn)行調(diào)用的,***個(gè)線程中,一個(gè)元素從容器1中移除,添加到容器2中。第二個(gè)線程中,該元素又從容器2移除添加到容器1中。這種做法會(huì)導(dǎo)致發(fā)生死鎖(如果在獲取***個(gè)鎖后,線程上下文剛好從一個(gè)線程切換到另一個(gè)線程,導(dǎo)致發(fā)生死鎖)。

int main() {     srand((unsigned int)time(NULL));       container<int> cont1;     cont1.add(1);     cont1.add(2);     cont1.add(3);       container<int> cont2;     cont2.add(4);     cont2.add(5);     cont2.add(6);       std::thread t1(exchange, std::ref(cont1), std::ref(cont2), 3);     std::thread t2(exchange, std::ref(cont2), std::ref(cont1), 6)       t1.join();     t2.join();       return 0; }

要解決這個(gè)問(wèn)題,可以使用std::lock來(lái)確保以避免發(fā)生死鎖的方式來(lái)獲取鎖。

void exchange(container<int>& cont1, container<int>& cont2, int value) {     std::lock(cont1._lock, cont2._lock);       cont1.remove(value);     cont2.add(value);       cont1._lock.unlock();     cont2._lock.unlock(); }

條件變量C++11 還提供了另外一種同步原語(yǔ),就是條件變量,它能使一個(gè)或多個(gè)線程進(jìn)入阻塞狀態(tài),直到接到另一個(gè)線程的通知,或者發(fā)生超時(shí)或虛假喚醒時(shí),才退出阻塞.在頭文件<condition_variable> 里對(duì)條件變量有兩種實(shí)現(xiàn):

condition_variable:要求任何在等待該條件變量的線程必須先獲取std::unique_lock鎖。

Condition_variable_any:是一種更加通用的實(shí)現(xiàn),可以用于任意滿足鎖的基本條件的類型(該實(shí)現(xiàn)只要提供了lock()和 unlock()方法即可)。因?yàn)槭褂盟ㄙM(fèi)的代價(jià)比較高(從性能和操作系統(tǒng)資源的角度來(lái)講),所以只有在提供了必不可少的額外的靈活性的條件下才提倡使 用它。

下面來(lái)講講條件變量的工作原理: 至少有一個(gè)線程在等待某個(gè)條件變?yōu)閠rue。等待的線程必須先獲取unique_lock  鎖。該鎖被傳遞給wait()方法,wait()方法會(huì)釋放互斥量,并將線程掛起,直到條件變量接收到信號(hào)。收到信號(hào)后,線程會(huì)被喚醒,同時(shí)該鎖也會(huì)被重 新獲取。

至少有一個(gè)線程發(fā)送信號(hào)使某個(gè)條件變?yōu)閠rue。可以使用notify_one()來(lái)發(fā)送信號(hào),同時(shí)喚醒一個(gè)正在等待該條件收到信號(hào)的處于阻塞狀態(tài)的線程,或者用notify_all()來(lái)喚醒在等待該條件的所有線程。

在多處理器系統(tǒng)中,因?yàn)橐恍?fù)雜情況,要想完全預(yù)測(cè)到條件被喚醒并不容易,還會(huì)出現(xiàn)虛假喚醒的情況。就是說(shuō),在沒人給條件變量發(fā)送信號(hào)的情況下,線程也可能會(huì)被喚醒。所以線程被喚醒后,還需要檢測(cè)條件是否為true。因?yàn)榭赡軙?huì)多次發(fā)生虛假喚醒,所以需要進(jìn)行循環(huán)檢測(cè)。

下面代碼是一個(gè)使用條件變量來(lái)同步線程的例子:幾個(gè)工作線程運(yùn)行時(shí)可能會(huì)產(chǎn)生錯(cuò)誤并將錯(cuò)誤代碼放到隊(duì)列里。記錄線程會(huì)從隊(duì)列里取出錯(cuò)誤代碼并輸出它 們來(lái)處理這些錯(cuò)誤。發(fā)生錯(cuò)誤的時(shí)候,工作線程會(huì)給記錄線程發(fā)信號(hào)。記錄線程一直在等待條件變量接收信號(hào)。為了避免發(fā)生虛假喚醒,該等待過(guò)程在循環(huán)檢測(cè)條件 的布爾值。

#include <thread> #include <mutex> #include <condition_variable> #include <iostream> #include <queue> #include <random>   std::mutex              g_lockprint; std::mutex              g_lockqueue; std::condition_variable g_queuecheck; std::queue<int>         g_codes; bool                    g_done; bool                    g_notified;   void workerfunc(int id, std::mt19937& generator) {     // print a starting message     {         std::unique_lock<std::mutex> locker(g_lockprint);         std::cout << "[worker " << id << "]\trunning..." << std::endl;     }       // simulate work     std::this_thread::sleep_for(std::chrono::seconds(1 + generator() % 5));       // simulate error     int errorcode = id*100+1;     {         std::unique_lock<std::mutex> locker(g_lockprint);         std::cout  << "[worker " << id << "]\tan error occurred: " << errorcode << std::endl;     }       // notify error to be logged     {         std::unique_lock<std::mutex> locker(g_lockqueue);         g_codes.push(errorcode);         g_notified = true;         g_queuecheck.notify_one();     } }   void loggerfunc() {     // print a starting message     {         std::unique_lock<std::mutex> locker(g_lockprint);         std::cout << "[logger]\trunning..." << std::endl;     }       // loop until end is signaled     while(!g_done)     {         std::unique_lock<std::mutex> locker(g_lockqueue);           while(!g_notified) // used to avoid spurious wakeups         {             g_queuecheck.wait(locker);         }           // if there are error codes in the queue process them         while(!g_codes.empty())         {             std::unique_lock<std::mutex> locker(g_lockprint);             std::cout << "[logger]\tprocessing error:  " << g_codes.front()  << std::endl;             g_codes.pop();         }           g_notified = false;     } }   int main() {     // initialize a random generator     std::mt19937 generator((unsigned int)std::chrono::system_clock::now().time_since_epoch().count());       // start the logger     std::thread loggerthread(loggerfunc);       // start the working threads     std::vector<std::thread> threads;     for(int i = 0; i < 5; ++i)     {         threads.push_back(std::thread(workerfunc, i+1, std::ref(generator)));     }       // work for the workers to finish     for(auto& t : threads)         t.join();       // notify the logger to finish and wait for it     g_done = true;     loggerthread.join();       return 0; }

運(yùn)行上述代碼,輸出結(jié)果如下(注意每次運(yùn)行,輸出結(jié)果都不一樣;因?yàn)槊總€(gè)工作線程運(yùn)行時(shí)都有一個(gè)隨機(jī)的休眠時(shí)間)。

[logger]        running... [worker 1]      running... [worker 2]      running... [worker 3]      running... [worker 4]      running... [worker 5]      running... [worker 1]      an error occurred: 101 [worker 2]      an error occurred: 201 [logger]        processing error:  101 [logger]        processing error:  201 [worker 5]      an error occurred: 501 [logger]        processing error:  501 [worker 3]      an error occurred: 301 [worker 4]      an error occurred: 401 [logger]        processing error:  301 [logger]        processing error:  401

上面看到的wait()方法有兩個(gè)重載:

  • ***個(gè)重載帶有鎖unique_lock;這個(gè)重載方法可以釋放鎖,阻塞線程,并把線程添加到正在等待這一條件變量的線程隊(duì)列里面。當(dāng)該條件變量收到信號(hào)或者發(fā)生虛假喚醒時(shí),線程就會(huì)被喚醒。它們其中任何一個(gè)發(fā)生時(shí),鎖都會(huì)被重新獲取,函數(shù)返回。

  • 第二個(gè)重載除了帶有鎖unique_lock外,還帶有循環(huán)判定直到返回false值;這個(gè)重載是用來(lái)避免發(fā)生虛假喚醒。它基本上等價(jià)于下面的語(yǔ)句:

while(!predicate())    wait(lock);

因此在上面的例子中,通過(guò)使用重載的wait()方法以及驗(yàn)證隊(duì)列狀態(tài)的判斷(空或不空),就可以避免使用布爾變量g_notified了。

void workerfunc(int id, std::mt19937& generator) {     // print a starting message     {         std::unique_lock<std::mutex> locker(g_lockprint);         std::cout << "[worker " << id << "]\trunning..." << std::endl;     }       // simulate work     std::this_thread::sleep_for(std::chrono::seconds(1 + generator() % 5));       // simulate error     int errorcode = id*100+1;     {         std::unique_lock<std::mutex> locker(g_lockprint);         std::cout << "[worker " << id << "]\tan error occurred: " << errorcode << std::endl;     }       // notify error to be logged     {         std::unique_lock<std::mutex> locker(g_lockqueue);         g_codes.push(errorcode);         g_queuecheck.notify_one();     } }   void loggerfunc() {     // print a starting message     {         std::unique_lock<std::mutex> locker(g_lockprint);         std::cout << "[logger]\trunning..." << std::endl;     }       // loop until end is signaled     while(!g_done)     {         std::unique_lock<std::mutex> locker(g_lockqueue);           g_queuecheck.wait(locker, [&](){return !g_codes.empty();});           // if there are error codes in the queue process them         while(!g_codes.empty())         {             std::unique_lock<std::mutex> locker(g_lockprint);             std::cout << "[logger]\tprocessing error:  " << g_codes.front() << std::endl;             g_codes.pop();         }     } }

除了這個(gè)重載的wait()方法,還有另外兩個(gè)類似的重載方法,也帶有避免虛假喚醒的判定。

  • Wait_for: 在條件變量收到信號(hào)或者指定的超時(shí)發(fā)生前,線程一直處于阻塞狀態(tài);

  • Wait_until:在條件變量收到信號(hào)或者指定的時(shí)刻到達(dá)之前,線程一直處于阻塞狀態(tài)。

這兩個(gè)函數(shù)的不帶有判定的重載返回cv_status狀態(tài),用來(lái)表明發(fā)生超時(shí)或者線程被喚醒是因?yàn)闂l件變量收到信號(hào)或者發(fā)生虛假喚醒。

該標(biāo)準(zhǔn)還提供了一個(gè)函數(shù)notify_all_at_thread_exit,它實(shí)現(xiàn)了一個(gè)機(jī)制,通知其他線程給定線程已經(jīng)運(yùn)行結(jié)束,并銷毀所有的 thread_local對(duì)象。該函數(shù)的引進(jìn)是因?yàn)樵谑褂昧藅hread_local后,采用除join()之外的其他機(jī)制來(lái)等待線程會(huì)導(dǎo)致不正確甚至致 命的行為發(fā)生。

因?yàn)閠hread_local的析構(gòu)函數(shù)會(huì)在等待中的線程恢復(fù)執(zhí)行和可能執(zhí)行結(jié)束的情況下被調(diào)用(可參考N3070和N2880得知更多信息)。

通常情況下,對(duì)這個(gè)函數(shù)的調(diào)用必須在線程生成之前。下面的例子描述了如何使用notify_all_at_thread_exit和condition_variable共同完成對(duì)兩個(gè)線程的同步操作:

std::mutex              g_lockprint; std::mutex              g_lock; std::condition_variable g_signal; bool                    g_done;   void workerfunc(std::mt19937& generator) {    {       std::unique_lock<std::mutex> locker(g_lockprint);       std::cout << "worker running..." << std::endl;    }      std::this_thread::sleep_for(std::chrono::seconds(1 + generator() % 5));      {       std::unique_lock<std::mutex> locker(g_lockprint);       std::cout << "worker finished..." << std::endl;    }      std::unique_lock<std::mutex> lock(g_lock);    g_done = true;    std::notify_all_at_thread_exit(g_signal, std::move(lock)); }   int main() {    // initialize a random generator    std::mt19937 generator((unsigned int)std::chrono::system_clock::now().time_since_epoch().count());      std::cout << "main running..." << std::endl;      std::thread worker(workerfunc, std::ref(generator));    worker.detach();      std::cout << "main crunching..." << std::endl;      std::this_thread::sleep_for(std::chrono::seconds(1 + generator() % 5));      {       std::unique_lock<std::mutex> locker(g_lockprint);       std::cout << "main waiting for worker..." << std::endl;    }      std::unique_lock<std::mutex> lock(g_lock);    while(!g_done) // avoid spurious wake-ups       g_signal.wait(lock);      std::cout << "main finished..." << std::endl;      return 0; }

如果工作線程在主線程執(zhí)行結(jié)束之前結(jié)束,輸出結(jié)果將如下:

main running... worker running... main crunching... worker finished... main waiting for worker... main finished...

如果主線程比工作線程更早結(jié)束,輸出結(jié)果將如下:

main running... worker running... main crunching... main waiting for worker... worker finished... main finished...

C++11標(biāo)準(zhǔn)可以讓C++開發(fā)者以一種標(biāo)準(zhǔn)的,獨(dú)立平臺(tái)的方式來(lái)編寫多線程。這篇文章大概講述了該標(biāo)準(zhǔn)所支持的線程和同步機(jī)制。頭文 件<thread>提供了thread類(和一些幫助函數(shù)),表明thread類是一個(gè)可執(zhí)行線程。頭文件<mutex>提供了 幾種互斥量的實(shí)現(xiàn)和對(duì)線程進(jìn)行同步訪問(wèn)的封裝類。頭文件<condition_variable>提供了條件變量的兩種實(shí)現(xiàn),這些實(shí)現(xiàn)使一個(gè) 或多個(gè)線程一直處于阻塞狀態(tài),直到接收到其他線程的通知,或發(fā)生超時(shí)或者有虛假喚醒發(fā)生時(shí)才會(huì)被喚醒。推薦讀者朋友可以閱讀其他資料來(lái)獲取更多的詳細(xì)信 息。

看完上述內(nèi)容,你們對(duì)怎么理解C++11 中的線程及鎖和條件變量有進(jìn)一步的了解嗎?如果還想了解更多知識(shí)或者相關(guān)內(nèi)容,請(qǐng)關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝大家的支持。

網(wǎng)頁(yè)標(biāo)題:怎么理解C++11中的線程及鎖和條件變量
網(wǎng)站地址:http://bm7419.com/article4/pcshoe.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供定制網(wǎng)站小程序開發(fā)、標(biāo)簽優(yōu)化、網(wǎng)頁(yè)設(shè)計(jì)公司、App開發(fā)微信小程序

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

成都網(wǎng)站建設(shè)