精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

C++11 中的線程、鎖和條件變量

開發 后端
C++11標準可以讓C++開發者以一種標準的,獨立平臺的方式來編寫多線程。這篇文章大概講述了該標準所支持的線程和同步機制。

線程

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

  1. #include <thread> 
  2.   
  3. void func() 
  4.    // do some work 
  5.   
  6. int main() 
  7.    std::thread t(func); 
  8.    t.join(); 
  9.   
  10.    return 0; 

上例中,t 是一個線程對象,函數func()運行于該線程中。對join()函數的調用將使調用線程(本例是指主線程)一直處于阻塞狀態,直到正在執行的線程t執行結束。如果線程函數返回某個值,該值也將被忽略。不過,該函數可以接收任意數量的參數。

  1. void func(int i, double d, const std::string& s) 
  2.     std::cout << i << ", " << d << ", " << s << std::endl; 
  3.   
  4. int main() 
  5.    std::thread t(func, 1, 12.50, "sample"); 
  6.    t.join(); 
  7.   
  8.    return 0; 

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

  1. void func(int& a) 
  2.    a++; 
  3.   
  4. int main() 
  5.    int a = 42; 
  6.    std::thread t(func, std::ref(a)); 
  7.    t.join(); 
  8.   
  9.    std::cout << a << std::endl; 
  10.   
  11.    return 0; 

該程序打印結果為43,但是如果不用std::ref把參數a進行封裝的話,輸出結果將為42.

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

swap:交換兩個線程對象的底層句柄。

Detach: 允許執行該方法的線程脫離其線程對象而繼續獨立執行。脫離后的線程不再是可結合線程(你不能等待它們執行結束)。

  1. int main() 
  2.     std::thread t(funct); 
  3.     t.detach(); 
  4.   
  5.     return 0; 

有一點非常重要,如果線程函數拋出異常,使用常規的try-catch語句是捕獲不到該異常的。換句話說,以下的做法是不可行的:

  1. try 
  2.     std::thread t1(func); 
  3.     std::thread t2(func); 
  4.   
  5.     t1.join(); 
  6.     t2.join(); 
  7. catch(const std::exception& ex) 
  8.     std::cout << ex.what() << std::endl; 

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

  1. std::mutex                       g_mutex; 
  2. std::vector<std::exception_ptr>  g_exceptions; 
  3.   
  4. void throw_function() 
  5.    throw std::exception("something wrong happened"); 
  6.   
  7. void func() 
  8.    try 
  9.    { 
  10.       throw_function(); 
  11.    } 
  12.    catch(...) 
  13.    { 
  14.       std::lock_guard<std::mutex> lock(g_mutex); 
  15.       g_exceptions.push_back(std::current_exception()); 
  16.    } 
  17.   
  18. int main() 
  19.    g_exceptions.clear(); 
  20.   
  21.    std::thread t(func); 
  22.    t.join(); 
  23.   
  24.    for(auto& e : g_exceptions) 
  25.    { 
  26.       try 
  27.       { 
  28.          if(e != nullptr) 
  29.          { 
  30.             std::rethrow_exception(e); 
  31.          } 
  32.       } 
  33.       catch(const std::exception& e) 
  34.       { 
  35.          std::cout << e.what() << std::endl; 
  36.       } 
  37.    } 
  38.   
  39.    return 0; 

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

在深入學習之前,有一點需要注意 &lt;thread&gt;頭文件在命名空間std::this_thread中提供了一些幫助函數:

  • get_id: 返回當前線程的id.
  • yield:在處于等待狀態時,可以讓調度器先運行其他可用的線程。
  • sleep_for:阻塞當前線程,時間不少于其參數指定的時間。
  • sleep_util:在參數指定的時間到達之前,使當前線程一直處于阻塞狀態。

#p#

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

  • Mutex: 提供了核心函數 lock() 和 unlock(),以及非阻塞方法的try_lock()方法,一旦互斥量不可用,該方法會立即返回。
  • Recursive_mutex:允許在同一個線程中對一個互斥量的多次請求。
  • Timed_mutex:同上面的mutex類似,但它還有另外兩個方法 try_lock_for() 和 try_lock_until(),分別用于在某個時間段里或者某個時刻到達之間獲取該互斥量。
  • Recursive_timed_mutex: 結合了timed_mutex 和recuseive_mutex的使用。

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

  1. #include <iostream> 
  2. #include <thread> 
  3. #include <mutex> 
  4. #include <chrono> 
  5.   
  6. std::mutex g_lock; 
  7.   
  8. void func() 
  9.     g_lock.lock(); 
  10.   
  11.     std::cout << "entered thread " << std::this_thread::get_id() << std::endl; 
  12.     std::this_thread::sleep_for(std::chrono::seconds(rand() % 10)); 
  13.     std::cout << "leaving thread " << std::this_thread::get_id() << std::endl; 
  14.   
  15.     g_lock.unlock(); 
  16.   
  17. int main() 
  18.     srand((unsigned int)time(0)); 
  19.   
  20.     std::thread t1(func); 
  21.     std::thread t2(func); 
  22.     std::thread t3(func); 
  23.   
  24.     t1.join(); 
  25.     t2.join(); 
  26.     t3.join(); 
  27.   
  28.     return 0; 

輸出結果如下所示:

  1. entered thread 10144 
  2. leaving thread 10144 
  3. entered thread 4188 
  4. leaving thread 4188 
  5. entered thread 3424 
  6. leaving thread 3424 

lock()和unlock()這兩個方法應該一目了然,***個方法用來對互斥量加鎖,如果互斥量不可用,便處于阻塞狀態。后者則用來對互斥量解鎖。

下面這個例子展示了一個簡單的線程安全容器(內部使用std::vector).這個容器帶有添加單個元素的add()方法和添加多個元素的addrange()方法,addrange()方法內部僅僅調用了add()方法。

注意:就像下面的評論里所指出的一樣,由于某些原因,包括使用了va_args,這不是一個標準的線程安全容器。而且,dump()方法也不是容器 的方法,從真正的實現上來說,它只是一個幫助(獨立的)函數。這個例子僅僅用來告訴大家一些有關互斥量的概念,而不是實現一個完全成熟的,無任何錯誤的線 程安全容器。

  1. template <typename T> 
  2. class container 
  3. {           
  4.     std::mutex _lock; 
  5.     std::vector<T> _elements; 
  6. public
  7.     void add(T element) 
  8.     { 
  9.         _lock.lock(); 
  10.         _elements.push_back(element); 
  11.         _lock.unlock(); 
  12.     } 
  13.   
  14.     void addrange(int num, ...) 
  15.     { 
  16.         va_list arguments; 
  17.   
  18.         va_start(arguments, num); 
  19.   
  20.         for (int i = 0; i < num; i++) 
  21.         { 
  22.             _lock.lock(); 
  23.             add(va_arg(arguments, T)); 
  24.             _lock.unlock(); 
  25.         } 
  26.   
  27.         va_end(arguments); 
  28.     } 
  29.   
  30.     void dump() 
  31.     { 
  32.         _lock.lock(); 
  33.         for(auto e : _elements) 
  34.             std::cout << e << std::endl; 
  35.         _lock.unlock(); 
  36.     } 
  37. }; 
  38.   
  39. void func(container<int>& cont) 
  40.     cont.addrange(3, rand(), rand(), rand()); 
  41.   
  42. int main() 
  43.     srand((unsigned int)time(0)); 
  44.   
  45.     container<int> cont; 
  46.   
  47.     std::thread t1(func, std::ref(cont)); 
  48.     std::thread t2(func, std::ref(cont)); 
  49.     std::thread t3(func, std::ref(cont)); 
  50.   
  51.     t1.join(); 
  52.     t2.join(); 
  53.     t3.join(); 
  54.   
  55.     cont.dump(); 
  56.   
  57.     return 0; 

運行該程序時,會進入死鎖狀態。原因是該容器試圖多次去獲取同一個互斥量,卻一直沒有釋放它,這樣是不可行的。

在這里,使用std::recursive_mutex就可以很好地解決這個問題,它允許同一個線程多次獲取同一個互斥量,可獲取的互斥量的***次數并沒有具體說明。但是一旦超過***次數,再對lock進行調用就會拋出std::system_error錯誤異常。

#p#

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

  1. template <typename T> 
  2. class container 
  3. {           
  4.     std::mutex _lock; 
  5.     std::vector<T> _elements; 
  6. public
  7.     void add(T element) 
  8.     { 
  9.         _lock.lock(); 
  10.         _elements.push_back(element); 
  11.         _lock.unlock(); 
  12.     } 
  13.   
  14.     void addrange(int num, ...) 
  15.     { 
  16.         va_list arguments; 
  17.   
  18.         va_start(arguments, num); 
  19.   
  20.         for (int i = 0; i < num; i++) 
  21.         { 
  22.             _lock.lock(); 
  23.             add(va_arg(arguments, T)); 
  24.             _lock.unlock(); 
  25.         } 
  26.   
  27.         va_end(arguments); 
  28.     } 
  29.   
  30.     void dump() 
  31.     { 
  32.         _lock.lock(); 
  33.         for(auto e : _elements) 
  34.             std::cout << e << std::endl; 
  35.         _lock.unlock(); 
  36.     } 
  37. }; 
  38.   
  39. void func(container<int>& cont) 
  40.     cont.addrange(3, rand(), rand(), rand()); 
  41.   
  42. int main() 
  43.     srand((unsigned int)time(0)); 
  44.   
  45.     container<int> cont; 
  46.   
  47.     std::thread t1(func, std::ref(cont)); 
  48.     std::thread t2(func, std::ref(cont)); 
  49.     std::thread t3(func, std::ref(cont)); 
  50.   
  51.     t1.join(); 
  52.     t2.join(); 
  53.     t3.join(); 
  54.   
  55.     cont.dump(); 
  56.   
  57.     return 0; 

修改后,就會得到下面的輸出結果。

  1. 6334 
  2. 18467 
  3. 41 
  4. 6334 
  5. 18467 
  6. 41 
  7. 6334 
  8. 18467 
  9. 41 

聰明的讀者會注意到每次調用func()都會產生相同的數字序列。這是因為種子數是線程本地化的,僅僅在主線程中調用了srand()對種子進行了初始化,在其他工作線程中并沒用進行初始化,所以每次都得到相同的數字序列。

顯式的加鎖和解鎖會導致一些問題,比如忘記解鎖或者請求加鎖的順序不正確,進而產生死鎖。該標準提供了一些類和函數幫助解決此類問題。這些封裝類保證了在RAII風格上互斥量使用的一致性,可以在給定的代碼范圍內自動加鎖和解鎖。封裝類包括:

Lock_guard:在構造對象時,它試圖去獲取互斥量的所有權(通過調用lock()),在析構對象時,自動釋放互斥量(通過調用unlock()).這是一個***的類。

Unique_lock:這個一通用的互斥量封裝類,不同于lock_guard,它還支持延遲加鎖,時間加鎖和遞歸加鎖以及鎖所有權的轉移和條件變量的使用。這也是一個***的類,但它是可移動類。

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

  1. template <typename T> 
  2. class container 
  3.     std::recursive_mutex _lock; 
  4.     std::vector<T> _elements; 
  5. public
  6.     void add(T element) 
  7.     { 
  8.         std::lock_guard<std::recursive_mutex> locker(_lock); 
  9.         _elements.push_back(element); 
  10.     } 
  11.   
  12.     void addrange(int num, ...) 
  13.     { 
  14.         va_list arguments; 
  15.   
  16.         va_start(arguments, num); 
  17.   
  18.         for (int i = 0; i < num; i++) 
  19.         { 
  20.             std::lock_guard<std::recursive_mutex> locker(_lock); 
  21.             add(va_arg(arguments, T)); 
  22.         } 
  23.   
  24.         va_end(arguments); 
  25.     } 
  26.   
  27.     void dump() 
  28.     { 
  29.         std::lock_guard<std::recursive_mutex> locker(_lock); 
  30.         for(auto e : _elements) 
  31.             std::cout << e << std::endl; 
  32.     } 
  33. }; 

#p#

有人也許會問,既然dump()方法并沒有對容器的狀態做任何修改,是不是應該定義為const方法呢?但是你如果將它定義為const,編譯器會報出下面的錯誤:

‘std::lock_guard<_Mutex>::lock_guard(_Mutex &)’ : cannot convert parameter 1 from ‘const std::recursive_mutex’ to ‘std::recursive_mutex &’

一個互斥量(不管使用的哪一種實現)必須要獲取和釋放,這就意味著要調用非const的lock()和unlock()方法。所以從邏輯上來 講,lock_guard的參數不能使const(因為如果該方法為const,互斥量也必需是const).解決這個問題的辦法就是將互斥量定義為可變 的mutable,Mutable允許在常函數中修改狀態。

不過,這種方法只能用于隱藏或者元狀態(就像對計算結果或查詢的數據進行緩存,以便下次調用時可以直接使用,不需要進行多次計算和查詢。再或者,對在一個對象的實際狀態起輔助作用的互斥量進行位的修改)。

  1. template <typename T> 
  2. class container 
  3.    mutable std::recursive_mutex _lock; 
  4.    std::vector<T> _elements; 
  5. public
  6.    void dump() const 
  7.    { 
  8.       std::lock_guard<std::recursive_mutex> locker(_lock); 
  9.       for(auto e : _elements) 
  10.          std::cout << e << std::endl; 
  11.    } 
  12. }; 

這些封裝類的構造函數可以重載,接受一個參數用來指明加鎖策略??捎玫牟呗匀缦拢?/p>

  • defer_lock of type defer_lock_t:不獲取互斥量的擁有權
  • try_to_lock of type try_to_lock_t:在不阻塞的情況下試圖獲取互斥量的擁有權
  • adopte_lock of type adopt_lock_t:假設調用線程已經擁有互斥量的所有權

這些策略的聲明如下:

  1. struct defer_lock_t { }; 
  2. struct try_to_lock_t { }; 
  3. struct adopt_lock_t { }; 
  4.   
  5. constexpr std::defer_lock_t defer_lock = std::defer_lock_t(); 
  6. constexpr std::try_to_lock_t try_to_lock = std::try_to_lock_t(); 
  7. constexpr std::adopt_lock_t adopt_lock = std::adopt_lock_t(); 

除了這些互斥量的封裝類,該標準還提供了兩個方法,用于對一個或多個互斥量進行加鎖。

  • lock:使用一種可以避免死鎖的算法對互斥量加鎖(通過調用lock(),try_lock()和unlock()).
  • try_lock():按照互斥量被指定的順序,試著通過調用try_lock()來對多個互斥量加鎖。

這是一個發生死鎖的例子:有一個用來存儲元素的容器和一個函數exchange(),該函數用來交換兩個容器中的元素。要成為線程安全函數,該函數通過獲取每個容器的互斥量,來對兩個容器的訪問進行同步操作。

  1. template <typename T> 
  2. class container 
  3. public
  4.     std::mutex _lock; 
  5.     std::set<T> _elements; 
  6.   
  7.     void add(T element) 
  8.     { 
  9.         _elements.insert(element); 
  10.     } 
  11.   
  12.     void remove(T element) 
  13.     { 
  14.         _elements.erase(element); 
  15.     } 
  16. }; 
  17.   
  18. void exchange(container<int>& cont1, container<int>& cont2, int value) 
  19.     cont1._lock.lock(); 
  20.     std::this_thread::sleep_for(std::chrono::seconds(1)); // <-- forces context switch to simulate the deadlock 
  21.     cont2._lock.lock();    
  22.   
  23.     cont1.remove(value); 
  24.     cont2.add(value); 
  25.   
  26.     cont1._lock.unlock(); 
  27.     cont2._lock.unlock(); 

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

  1. int main() 
  2.     srand((unsigned int)time(NULL)); 
  3.   
  4.     container<int> cont1; 
  5.     cont1.add(1); 
  6.     cont1.add(2); 
  7.     cont1.add(3); 
  8.   
  9.     container<int> cont2; 
  10.     cont2.add(4); 
  11.     cont2.add(5); 
  12.     cont2.add(6); 
  13.   
  14.     std::thread t1(exchange, std::ref(cont1), std::ref(cont2), 3); 
  15.     std::thread t2(exchange, std::ref(cont2), std::ref(cont1), 6) 
  16.   
  17.     t1.join(); 
  18.     t2.join(); 
  19.   
  20.     return 0; 

要解決這個問題,可以使用std::lock來確保以避免發生死鎖的方式來獲取鎖。

  1. void exchange(container<int>& cont1, container<int>& cont2, int value) 
  2.     std::lock(cont1._lock, cont2._lock); 
  3.   
  4.     cont1.remove(value); 
  5.     cont2.add(value); 
  6.   
  7.     cont1._lock.unlock(); 
  8.     cont2._lock.unlock(); 

#p#

條件變量C++11 還提供了另外一種同步原語,就是條件變量,它能使一個或多個線程進入阻塞狀態,直到接到另一個線程的通知,或者發生超時或虛假喚醒時,才退出阻塞.在頭文件<condition_variable> 里對條件變量有兩種實現:

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

Condition_variable_any:是一種更加通用的實現,可以用于任意滿足鎖的基本條件的類型(該實現只要提供了lock()和 unlock()方法即可)。因為使用它花費的代價比較高(從性能和操作系統資源的角度來講),所以只有在提供了必不可少的額外的靈活性的條件下才提倡使 用它。

下面來講講條件變量的工作原理: 至少有一個線程在等待某個條件變為true。等待的線程必須先獲取unique_lock 鎖。該鎖被傳遞給wait()方法,wait()方法會釋放互斥量,并將線程掛起,直到條件變量接收到信號。收到信號后,線程會被喚醒,同時該鎖也會被重 新獲取。

至少有一個線程發送信號使某個條件變為true。可以使用notify_one()來發送信號,同時喚醒一個正在等待該條件收到信號的處于阻塞狀態的線程,或者用notify_all()來喚醒在等待該條件的所有線程。

在多處理器系統中,因為一些復雜情況,要想完全預測到條件被喚醒并不容易,還會出現虛假喚醒的情況。就是說,在沒人給條件變量發送信號的情況下,線程也可能會被喚醒。所以線程被喚醒后,還需要檢測條件是否為true。因為可能會多次發生虛假喚醒,所以需要進行循環檢測。

下面代碼是一個使用條件變量來同步線程的例子:幾個工作線程運行時可能會產生錯誤并將錯誤代碼放到隊列里。記錄線程會從隊列里取出錯誤代碼并輸出它 們來處理這些錯誤。發生錯誤的時候,工作線程會給記錄線程發信號。記錄線程一直在等待條件變量接收信號。為了避免發生虛假喚醒,該等待過程在循環檢測條件 的布爾值。

 

  1. #include <thread> 
  2. #include <mutex> 
  3. #include <condition_variable> 
  4. #include <iostream> 
  5. #include <queue> 
  6. #include <random> 
  7.   
  8. std::mutex              g_lockprint; 
  9. std::mutex              g_lockqueue; 
  10. std::condition_variable g_queuecheck; 
  11. std::queue<int>         g_codes; 
  12. bool                    g_done; 
  13. bool                    g_notified; 
  14.   
  15. void workerfunc(int id, std::mt19937& generator) 
  16.     // print a starting message 
  17.     { 
  18.         std::unique_lock<std::mutex> locker(g_lockprint); 
  19.         std::cout << "[worker " << id << "]\trunning..." << std::endl; 
  20.     } 
  21.   
  22.     // simulate work 
  23.     std::this_thread::sleep_for(std::chrono::seconds(1 + generator() % 5)); 
  24.   
  25.     // simulate error 
  26.     int errorcode = id*100+1; 
  27.     { 
  28.         std::unique_lock<std::mutex> locker(g_lockprint); 
  29.         std::cout  << "[worker " << id << "]\tan error occurred: " << errorcode << std::endl; 
  30.     } 
  31.   
  32.     // notify error to be logged 
  33.     { 
  34.         std::unique_lock<std::mutex> locker(g_lockqueue); 
  35.         g_codes.push(errorcode); 
  36.         g_notified = true
  37.         g_queuecheck.notify_one(); 
  38.     } 
  39.   
  40. void loggerfunc() 
  41.     // print a starting message 
  42.     { 
  43.         std::unique_lock<std::mutex> locker(g_lockprint); 
  44.         std::cout << "[logger]\trunning..." << std::endl; 
  45.     } 
  46.   
  47.     // loop until end is signaled 
  48.     while(!g_done) 
  49.     { 
  50.         std::unique_lock<std::mutex> locker(g_lockqueue); 
  51.   
  52.         while(!g_notified) // used to avoid spurious wakeups 
  53.         { 
  54.             g_queuecheck.wait(locker); 
  55.         } 
  56.   
  57.         // if there are error codes in the queue process them 
  58.         while(!g_codes.empty()) 
  59.         { 
  60.             std::unique_lock<std::mutex> locker(g_lockprint); 
  61.             std::cout << "[logger]\tprocessing error:  " << g_codes.front()  << std::endl; 
  62.             g_codes.pop(); 
  63.         } 
  64.   
  65.         g_notified = false
  66.     } 
  67.   
  68. int main() 
  69.     // initialize a random generator 
  70.     std::mt19937 generator((unsigned int)std::chrono::system_clock::now().time_since_epoch().count()); 
  71.   
  72.     // start the logger 
  73.     std::thread loggerthread(loggerfunc); 
  74.   
  75.     // start the working threads 
  76.     std::vector<std::thread> threads; 
  77.     for(int i = 0; i < 5; ++i) 
  78.     { 
  79.         threads.push_back(std::thread(workerfunc, i+1, std::ref(generator))); 
  80.     } 
  81.   
  82.     // work for the workers to finish 
  83.     for(auto& t : threads) 
  84.         t.join(); 
  85.   
  86.     // notify the logger to finish and wait for it 
  87.     g_done = true
  88.     loggerthread.join(); 
  89.   
  90.     return 0; 

運行上述代碼,輸出結果如下(注意每次運行,輸出結果都不一樣;因為每個工作線程運行時都有一個隨機的休眠時間)。

  1. [logger]        running... 
  2. [worker 1]      running... 
  3. [worker 2]      running... 
  4. [worker 3]      running... 
  5. [worker 4]      running... 
  6. [worker 5]      running... 
  7. [worker 1]      an error occurred: 101 
  8. [worker 2]      an error occurred: 201 
  9. [logger]        processing error:  101 
  10. [logger]        processing error:  201 
  11. [worker 5]      an error occurred: 501 
  12. [logger]        processing error:  501 
  13. [worker 3]      an error occurred: 301 
  14. [worker 4]      an error occurred: 401 
  15. [logger]        processing error:  301 
  16. [logger]        processing error:  401 

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

  • ***個重載帶有鎖unique_lock;這個重載方法可以釋放鎖,阻塞線程,并把線程添加到正在等待這一條件變量的線程隊列里面。當該條件變量收到信號或者發生虛假喚醒時,線程就會被喚醒。它們其中任何一個發生時,鎖都會被重新獲取,函數返回。
  • 第二個重載除了帶有鎖unique_lock外,還帶有循環判定直到返回false值;這個重載是用來避免發生虛假喚醒。它基本上等價于下面的語句:
  1. while(!predicate()) 
  2.    wait(lock); 

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

  1. void workerfunc(int id, std::mt19937& generator) 
  2.     // print a starting message 
  3.     { 
  4.         std::unique_lock<std::mutex> locker(g_lockprint); 
  5.         std::cout << "[worker " << id << "]\trunning..." << std::endl; 
  6.     } 
  7.   
  8.     // simulate work 
  9.     std::this_thread::sleep_for(std::chrono::seconds(1 + generator() % 5)); 
  10.   
  11.     // simulate error 
  12.     int errorcode = id*100+1; 
  13.     { 
  14.         std::unique_lock<std::mutex> locker(g_lockprint); 
  15.         std::cout << "[worker " << id << "]\tan error occurred: " << errorcode << std::endl; 
  16.     } 
  17.   
  18.     // notify error to be logged 
  19.     { 
  20.         std::unique_lock<std::mutex> locker(g_lockqueue); 
  21.         g_codes.push(errorcode); 
  22.         g_queuecheck.notify_one(); 
  23.     } 
  24.   
  25. void loggerfunc() 
  26.     // print a starting message 
  27.     { 
  28.         std::unique_lock<std::mutex> locker(g_lockprint); 
  29.         std::cout << "[logger]\trunning..." << std::endl; 
  30.     } 
  31.   
  32.     // loop until end is signaled 
  33.     while(!g_done) 
  34.     { 
  35.         std::unique_lock<std::mutex> locker(g_lockqueue); 
  36.   
  37.         g_queuecheck.wait(locker, [&](){return !g_codes.empty();}); 
  38.   
  39.         // if there are error codes in the queue process them 
  40.         while(!g_codes.empty()) 
  41.         { 
  42.             std::unique_lock<std::mutex> locker(g_lockprint); 
  43.             std::cout << "[logger]\tprocessing error:  " << g_codes.front() << std::endl; 
  44.             g_codes.pop(); 
  45.         } 
  46.     } 

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

  • Wait_for: 在條件變量收到信號或者指定的超時發生前,線程一直處于阻塞狀態;
  • Wait_until:在條件變量收到信號或者指定的時刻到達之前,線程一直處于阻塞狀態。

這兩個函數的不帶有判定的重載返回cv_status狀態,用來表明發生超時或者線程被喚醒是因為條件變量收到信號或者發生虛假喚醒。

該標準還提供了一個函數notify_all_at_thread_exit,它實現了一個機制,通知其他線程給定線程已經運行結束,并銷毀所有的 thread_local對象。該函數的引進是因為在使用了thread_local后,采用除join()之外的其他機制來等待線程會導致不正確甚至致 命的行為發生。

因為thread_local的析構函數會在等待中的線程恢復執行和可能執行結束的情況下被調用(可參考N3070和N2880得知更多信息)。

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

  1. std::mutex              g_lockprint; 
  2. std::mutex              g_lock; 
  3. std::condition_variable g_signal; 
  4. bool                    g_done; 
  5.   
  6. void workerfunc(std::mt19937& generator) 
  7.    { 
  8.       std::unique_lock<std::mutex> locker(g_lockprint); 
  9.       std::cout << "worker running..." << std::endl; 
  10.    } 
  11.   
  12.    std::this_thread::sleep_for(std::chrono::seconds(1 + generator() % 5)); 
  13.   
  14.    { 
  15.       std::unique_lock<std::mutex> locker(g_lockprint); 
  16.       std::cout << "worker finished..." << std::endl; 
  17.    } 
  18.   
  19.    std::unique_lock<std::mutex> lock(g_lock); 
  20.    g_done = true
  21.    std::notify_all_at_thread_exit(g_signal, std::move(lock)); 
  22.   
  23. int main() 
  24.    // initialize a random generator 
  25.    std::mt19937 generator((unsigned int)std::chrono::system_clock::now().time_since_epoch().count()); 
  26.   
  27.    std::cout << "main running..." << std::endl; 
  28.   
  29.    std::thread worker(workerfunc, std::ref(generator)); 
  30.    worker.detach(); 
  31.   
  32.    std::cout << "main crunching..." << std::endl; 
  33.   
  34.    std::this_thread::sleep_for(std::chrono::seconds(1 + generator() % 5)); 
  35.   
  36.    { 
  37.       std::unique_lock<std::mutex> locker(g_lockprint); 
  38.       std::cout << "main waiting for worker..." << std::endl; 
  39.    } 
  40.   
  41.    std::unique_lock<std::mutex> lock(g_lock); 
  42.    while(!g_done) // avoid spurious wake-ups 
  43.       g_signal.wait(lock); 
  44.   
  45.    std::cout << "main finished..." << std::endl; 
  46.   
  47.    return 0; 

如果工作線程在主線程執行結束之前結束,輸出結果將如下:

  1. main running... 
  2. worker running... 
  3. main crunching... 
  4. worker finished... 
  5. main waiting for worker... 
  6. main finished... 

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

  1. main running... 
  2. worker running... 
  3. main crunching... 
  4. main waiting for worker... 
  5. worker finished... 
  6. main finished... 

結束語

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

原文鏈接:http://blog.jobbole.com/44409/

責任編輯:陳四芳 來源: 伯樂在線
相關推薦

2013-05-30 00:49:36

C++11C++條件變量

2025-05-22 08:10:00

C++條件變量編程

2013-12-23 09:48:43

C++鎖定模式

2020-09-23 16:31:38

C++C++11啟動線程

2024-02-21 23:43:11

C++11C++開發

2023-09-22 22:27:54

autoC++11

2024-05-29 13:21:21

2012-12-25 10:52:23

IBMdW

2020-06-01 21:07:33

C11C++11內存

2011-08-19 09:41:56

C++

2024-06-24 08:10:00

C++互斥鎖

2025-05-21 08:00:00

C++11關鍵字多線程

2013-09-25 14:20:46

2021-06-11 10:53:40

Folly組件開發

2020-12-09 10:55:25

ArrayvectorLinux

2023-09-24 13:58:20

C++1auto

2016-11-23 16:08:24

Python處理器分布式系統

2024-10-14 16:25:59

C#線程鎖代碼

2013-11-29 09:51:26

C++雙重檢查鎖定

2011-10-13 10:21:01

C++
點贊
收藏

51CTO技術棧公眾號

丰满少妇中文字幕| 国产三级中文字幕| 中文字幕免费观看| 欧美电影免费| 日韩精品一区二区三区视频播放| 亚洲国产精品无码av| 美女欧美视频在线观看免费 | 一区二区三区欧洲区| 亚洲国产日日夜夜| 台湾成人av| wwwav在线播放| 两个人看的在线视频www| 国产精品一区二区果冻传媒| 26uuu亚洲伊人春色| 欧美 日韩 国产在线| 草草地址线路①屁屁影院成人| 中文字幕免费高清| 激情综合激情五月| 天堂网免费视频| 欧美综合视频在线| 成人午夜视频在线播放| 国产在线激情| 91麻豆福利精品推荐| 国产女人18毛片水18精品| xxxxxx国产| 中文字幕综合| 一区二区三区不卡视频| 91久久精品国产91久久性色tv| 亚洲 欧美 日韩 在线| 精品国产乱码久久久| 亚洲激情视频在线| 亚洲天堂伊人网| 69堂精品视频在线播放| 一区二区高清视频在线观看| 亚洲巨乳在线观看| 日韩欧美电影在线观看| 国产米奇在线777精品观看| 国产精品成av人在线视午夜片| 欧美亚洲天堂网| 天天久久综合| 综合国产在线观看| xxxx日本免费| 日韩中文字幕无砖| 91精品婷婷国产综合久久| xxxx一级片| 二区三区不卡| 欧美日韩在线视频一区| 国产黄色激情视频| av网站导航在线观看免费| 中文字幕不卡在线| 婷婷精品国产一区二区三区日韩| 玖玖综合伊人| 91美女福利视频| 久久精品国产精品国产精品污 | 韩国v欧美v亚洲v日本v| 国产精品国模在线| 久久精品视频5| 国产日本精品| 欧美中文字幕在线播放| 久久不卡免费视频| 久久国产高清| 热re99久久精品国产66热| 国产一级片毛片| 国产农村妇女毛片精品久久莱园子| 98精品在线视频| 日本在线免费观看| 国产日韩精品视频一区二区三区| 亚州成人av在线| www亚洲视频| 日韩精品91亚洲二区在线观看| 国产精品久久久999| ,亚洲人成毛片在线播放| 久久99精品视频| 5566中文字幕一区二区| 免费观看黄一级视频| 99精品视频一区二区三区| 欧美日韩精品不卡| 99青草视频在线播放视| 亚洲人妖av一区二区| 欧美视频在线第一页| 91超碰在线播放| 色呦呦国产精品| 亚洲老女人av| 精品国产一区二| 亚洲大胆人体在线| 李宗瑞91在线正在播放| 色97色成人| 欧美激情网友自拍| 青青青国产在线| 另类成人小视频在线| 97人人模人人爽人人少妇| 人妻少妇精品无码专区久久| 国产日韩三级在线| 色乱码一区二区三区熟女 | 不卡一区综合视频| 久久激情视频免费观看| 国产极品在线播放| 日韩精品一级中文字幕精品视频免费观看 | 日韩一区二区三区高清免费看看| 最新国产精品自拍| 久久99国产精一区二区三区| 久久精品99久久久香蕉| 日韩精品视频免费播放| 蜜臀久久久99精品久久久久久| 91精品免费| 蜜桃视频在线入口www| 成人免费视频在线观看| 欧美 日韩 国产 高清| 日本久久久久| 日韩高清免费观看| 精品国产视频在线观看| 免费日韩视频| 国产成人精品福利一区二区三区 | 国产中文字幕免费观看| 久久亚洲国产精品尤物| 亚洲国产精品嫩草影院久久| 蜜桃视频最新网址| 日韩精品视频网| 97欧洲一区二区精品免费| 91啦中文在线| 欧美日韩亚洲国产一区| 免费欧美一级片| 欧美自拍偷拍| 456亚洲影院| www.好吊色| 国产精品久久久久影院老司| 日韩欧美国产免费| 国产精品久久久久av蜜臀| 久久久999精品视频| 波多野结衣视频观看| av在线不卡免费看| 日韩a级在线观看| 日本一区二区三区播放| 久久精品视频导航| 亚洲精品无码久久久久| 91免费看`日韩一区二区| 国产乱子伦精品视频| 亚洲欧洲二区| 日韩中文在线中文网三级| 国产超碰人人爽人人做人人爱| 丁香六月综合激情| 精品无码av无码免费专区| 疯狂欧洲av久久成人av电影| 日韩在线观看免费全集电视剧网站 | www.欧美com| 亚洲午夜精品一区 二区 三区| 国产精品久久久久久亚洲影视 | 久久精品91久久久久久再现| 亚洲欧美一区二区三区在线观看| 99久久夜色精品国产网站| 国产乱人伦精品一区二区三区| 精品视频一区二区三区| 久久久国产一区二区| 国产精品高潮呻吟av| 国产精品国产三级国产专播品爱网| 男女男精品视频站| 欧美最新另类人妖| 国产精品入口免费视| yiren22亚洲综合伊人22| 欧美中文字幕一区二区三区亚洲| 欧美黄色一级生活片| 日韩电影免费在线| 午夜精品一区二区三区四区 | 99re热精品| 天堂av在线电影| 精品少妇一区二区三区日产乱码| 久草视频在线免费看| 成人免费观看av| 99精品在线免费视频| 五月激激激综合网色播| 清纯唯美亚洲激情| aⅴ在线视频男人的天堂| 欧美精品免费视频| 国产一级aa大片毛片| 成人毛片老司机大片| av动漫在线观看| av中文一区| 成人一区二区电影| 国产99re66在线视频| 日韩高清不卡av| 一二三四区视频| 一区二区三区产品免费精品久久75| 国产视频精品视频| 久久亚洲图片| 黄色一级视频播放| 麻豆精品av| 国产精品视频久久| 调教一区二区| 亚洲视频网站在线观看| 国产一区二区在线视频聊天| 一区二区视频免费在线观看| 欧美 变态 另类 人妖| 男男视频亚洲欧美| 日韩精品久久一区二区| 国产乱码精品一区二区三区四区| 亚洲精品免费av| 成人免费影院| 久久在线精品视频| 亚洲欧美日韩免费| 69堂成人精品免费视频| 免费观看一区二区三区毛片| 国产精品卡一卡二| 国产精品久久久久久亚洲av| 美女一区二区三区在线观看| 男女私大尺度视频| 91亚洲国产成人久久精品| 国产一区二区无遮挡| 免费成人毛片| 琪琪亚洲精品午夜在线| 伊人影院在线视频| 一本一道久久a久久精品逆3p | 一区二区三区美女xx视频| www.热久久| 欧美天天综合网| 自拍偷拍欧美亚洲| 一区二区三区日本| 精品手机在线视频| 2017欧美狠狠色| 无码人妻精品一区二区三| 奇米影视一区二区三区小说| 国产老熟妇精品观看| 在线中文字幕亚洲| 午夜久久资源| 色棕色天天综合网| 国产日韩在线一区二区三区| 中文幕av一区二区三区佐山爱| 国产99视频精品免视看7| 男男gaygays亚洲| 伦伦影院午夜日韩欧美限制| 粉嫩一区二区三区国产精品| 亚洲精品短视频| 国产黄色av片| 欧美精品九九99久久| 亚洲精品一区二三区| 欧美日韩中文字幕日韩欧美| 日本特黄特色aaa大片免费| 亚洲欧美偷拍另类a∨色屁股| 快灬快灬一下爽蜜桃在线观看| 91美女福利视频| 国产一级二级视频| 成人三级伦理片| 精品人妻在线视频| 国产99久久久国产精品潘金 | 精品一区二区三区在线观看视频| 国产精品美女在线观看| 99久久久国产精品免费调教网站| 国产成人精品免高潮在线观看| av资源亚洲| 欧美在线视频观看免费网站| 国产精品电影| 538国产精品一区二区在线 | 黄色国产在线| 亚洲日本中文字幕| 九色视频成人自拍| 亚洲日本成人网| shkd中文字幕久久在线观看| 在线观看亚洲区| 免费超碰在线| 欧美理论电影在线观看| 污片视频在线免费观看| 久久久免费高清电视剧观看| 国产精品蜜臀| 韩国三级电影久久久久久| 丁香花视频在线观看| 51精品在线观看| 欧美123区| 成人妇女淫片aaaa视频| 久久久久毛片免费观看| 成人做爽爽免费视频| 在线观看视频一区二区三区| 国产一区二区久久久| 亚洲自拍电影| 午夜精品亚洲一区二区三区嫩草| 91久久国产| 男的插女的下面视频| 国产欧美日本| 国产v亚洲v天堂无码久久久| 奇米综合一区二区三区精品视频| 午夜大片在线观看| av一区二区三区在线| 欧美多人猛交狂配| 中文字幕一区二| 久草免费新视频| 色婷婷av一区二区三区之一色屋| 自拍偷拍色综合| 日韩精品一区二区三区中文不卡 | 日韩欧美高清一区二区三区| 精品国产乱码一区二区三区四区| 国产探花在线精品一区二区| 香蕉视频在线网址| 亚洲综合日韩| 中文字幕一区二区在线观看视频| 高清av一区二区| 成人片黄网站色大片免费毛片| 中文字幕亚洲一区二区av在线| 国产91av视频| 欧美三级乱人伦电影| 亚洲精品久久久蜜桃动漫| 一本久久综合亚洲鲁鲁| 在线观看中文字幕的网站| 日本精品久久久久影院| 久久九九精品视频| 欧美午夜精品久久久久久蜜| 午夜欧美精品久久久久久久| 日韩精品一区二区三区不卡| 国产成人免费视| 香蕉成人在线视频| 精品美女永久免费视频| 国产露脸国语对白在线| 亚洲欧美国内爽妇网| 午夜成年人在线免费视频| 国产精品久久久久久久久久99| 99精品中文字幕在线不卡| 一区二区三区欧美在线| 先锋亚洲精品| 国产精品熟妇一区二区三区四区| 国产精品全国免费观看高清 | 国产欧美成人| 99热这里只有精品2| 国产精品久久久久久久久免费樱桃 | 99精品在线| 蜜臀av午夜一区二区三区| 成人丝袜高跟foot| 国产97免费视频| 欧美日韩久久一区| 精品亚洲成a人片在线观看| 国模精品一区二区三区色天香| 极品魔鬼身材女神啪啪精品| 999国产精品一区| 国产一区在线观| 91精品91| 一本岛在线视频| 久久久精品2019中文字幕之3| 精品视频一区二区在线观看| 欧美群妇大交群中文字幕| 免费a级毛片在线观看| 66m—66摸成人免费视频| 97se亚洲| 成人免费网站入口| 国产99精品在线观看| 黄色一级片中国| 欧美一区二区三区在线观看 | 久久精品一区二区| 久久久久99精品成人片三人毛片| 亚洲精品在线观看视频| 国内小视频在线看| 国产欧美一区二区在线播放| 怡红院精品视频在线观看极品| av影片在线播放| 一区二区在线免费| 亚洲精品18p| 午夜欧美大片免费观看| 精品网站aaa| 免费看的黄色大片| 久久影院午夜论| 一级片在线观看免费| 国产一区二区三区在线观看视频| 日韩毛片免费观看| 亚洲精品一区二区毛豆| 精品一区免费av| 日本妇女毛茸茸| 精品成人一区二区| 欧美电影网站| 亚洲资源在线网| 高清在线成人网| 国产精品黄色网| 亚洲图片欧美日产| 成人日韩视频| 成人免费性视频| 国产亚洲欧美中文| 国产又黄又猛又爽| 久久久久中文字幕2018| 亚洲综合福利| 亚洲一级片网站| 一区二区激情小说| 免费国产在线观看| 91在线观看免费高清| 99在线精品免费视频九九视| 亚洲码无人客一区二区三区| 欧美日韩亚洲综合一区| 宅男网站在线免费观看| 久久资源av| 久草这里只有精品视频| 久久综合色综合| 亚洲天堂免费观看| 91成人app| 欧美啪啪免费视频| 国产精品久久久久毛片软件| 99视频国产精品免费观看a| 7m第一福利500精品视频| 日韩一区二区三区免费播放| 日本wwwwwww| 欧美在线观看视频一区二区三区 | 国产欧美高清视频在线| 国产女同无遮挡互慰高潮91| 亚洲国产欧美在线| 超碰97在线免费观看| 国产精品xxxx| 久久99久久99精品免视看婷婷 | 欧美亚洲丝袜传媒另类|