日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
C++11中的線程、鎖和條件變量

線程

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

 
 
 
  1. #include 
  2.  
  3. void func()
  4. {
  5.    // do some work
  6. }
  7.  
  8. int main()
  9. {
  10.    std::thread t(func);
  11.    t.join();
  12.  
  13.    return 0;
  14. }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

#p#

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

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

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

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

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

 
 
 
  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()這兩個方法應(yīng)該一目了然,***個方法用來對互斥量加鎖,如果互斥量不可用,便處于阻塞狀態(tài)。后者則用來對互斥量解鎖。

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

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

 
 
 
  1. template 
  2. class container
  3. {          
  4.     std::mutex _lock;
  5.     std::vector _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& cont)
  40. {
  41.     cont.addrange(3, rand(), rand(), rand());
  42. }
  43.  
  44. int main()
  45. {
  46.     srand((unsigned int)time(0));
  47.  
  48.     container cont;
  49.  
  50.     std::thread t1(func, std::ref(cont));
  51.     std::thread t2(func, std::ref(cont));
  52.     std::thread t3(func, std::ref(cont));
  53.  
  54.     t1.join();
  55.     t2.join();
  56.     t3.join();
  57.  
  58.     cont.dump();
  59.  
  60.     return 0;
  61. }

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

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

#p#

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

 
 
 
  1. template 
  2. class container
  3. {          
  4.     std::mutex _lock;
  5.     std::vector _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& cont)
  40. {
  41.     cont.addrange(3, rand(), rand(), rand());
  42. }
  43.  
  44. int main()
  45. {
  46.     srand((unsigned int)time(0));
  47.  
  48.     container cont;
  49.  
  50.     std::thread t1(func, std::ref(cont));
  51.     std::thread t2(func, std::ref(cont));
  52.     std::thread t3(func, std::ref(cont));
  53.  
  54.     t1.join();
  55.     t2.join();
  56.     t3.join();
  57.  
  58.     cont.dump();
  59.  
  60.     return 0;
  61. }

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

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

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

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

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

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

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

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

#p#

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

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

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

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

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

這些封裝類的構(gòu)造函數(shù)可以重載,接受一個參數(shù)用來指明加鎖策略??捎玫牟呗匀缦拢?/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)

這些策略的聲明如下:

 
 
 
  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();

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

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

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

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

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

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

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

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

#p#

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

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

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

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

至少有一個線程發(fā)送信號使某個條件變?yōu)閠rue??梢允褂胣otify_one()來發(fā)送信號,同時喚醒一個正在等待該條件收到信號的處于阻塞狀態(tài)的線程,或者用notify_all()來喚醒在等待該條件的所有線程。

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

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

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

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

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

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

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

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

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

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

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

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

通常情況下,對這個函數(shù)的調(diào)用必須在線程生成之前。下面的例子描述了如何使用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.    {
  9.       std::unique_lock 
    文章名稱:C++11中的線程、鎖和條件變量
    文章出自:http://www.dlmjj.cn/article/djceheh.html