1. 程式人生 > >std::thread執行緒庫詳解(4)

std::thread執行緒庫詳解(4)

## 目錄 - [目錄](#目錄) - [前言](#前言) - [條件變數](#條件變數) - [一些需要注意的地方](#一些需要注意的地方) - [總結](#總結) ## 前言 本文主要介紹了多執行緒中的條件變數,條件變數在多執行緒同步中用的也比較多。我第一次接觸到條件變數的時候是在完成一個多執行緒佇列的時候。條件變數用在佇列沒有資料時,等待入隊執行緒入隊資料。相比較於鎖的使用,條件變數的使用更為複雜,使用時需要注意的部分也更多。本文將會完成一個阻塞佇列(對普通佇列進行一個簡單的包裝),以此來完成條件變數的介紹。 ## 條件變數 條件變數(`std::condition_variable`)的使用需要鎖的幫助。所以在定義阻塞佇列時,私有成員包含了一個鎖。 ```C++ template class BlockingQueue { public: int pop(T &&data); int push(T &&data); private: std::queue m_queue; std::condition_variable cond; std::mutex mutex; }; ``` 可以看到,阻塞佇列的實現只有`pop`和`push`兩個部分,由於沒有容量限制,所以只有單向的條件變數。首先是`pop`的實現, ```C++ int pop(T &data) { std::unique_lock lock(mutex); if (m_queue.empty()) { return -1; } else { data = m_queue.front(); m_queue.pop(); return 0; } } ``` 如果不使用條件變數,很容易實現一個非阻塞的`pop`方法,如果佇列中有資料,則返回資料,並返回0。如果沒有,直接返回-1。但是如果我們想要實現在佇列中沒有資料的時候,程式不是直接返回而是等待直到有資料,那麼最簡單的方法就是藉助條件變數`std::condition_variable`(其實只用鎖也能實現,但是比較麻煩)。 ```C++ int pop(T &data) { std::unique_lock lock(mutex); while (m_queue.empty()) { cond.wait(lock); } data = m_queue.front(); m_queue.pop(); return 0; } ``` 需要注意的是,`while(m_queue.empty)`這一部分,在`cppreference.com`中也有明確的說明,條件變數可能存在虛假的喚醒,所以需要檢查是否滿足條件。當然,C++也提供了`wait`的一個過載函式來實現對喚醒條件的檢查。同時它也有超時的版本`wait_for`和`wait_until`。 ```C++ int pop(T &data) { std::unique_lock lock(mutex); cond.wait(lock, [&]() {return m_queue.empty();}); data = m_queue.front(); m_queue.pop(); return 0; } ``` 然後是對`push`的實現, ```C++ int push(T &data) { std::unique_lock lock(mutex); m_queue.push(data); cond.notify_one(); return 0; } ``` 這裡使用的是`notify_one`,也有`notify_all`但是沒有必要在這使用。然後進行合併測試,可以得到以下的結果 ![](https://img2020.cnblogs.com/blog/2105008/202102/2105008-20210207155110252-1606348136.png) 除了`std::condition_variable`以外,還有一個`std::condition_variable_any`,它可以支援任意的鎖,在使用上變化不大。 ## 一些需要注意的地方 1. 在喚醒執行緒之後,會進行加鎖的操作。所以如果邏輯允許,記得手動釋放鎖; 2. 注意虛假喚醒的情況; 3. 如果記得退出執行緒。 ## 總結 本文通過一個簡單的例子簡單介紹了一下條件變數的使用。下一篇將會介紹訊號量和latch barrier,這兩個都是C++20新出現的特性。 部落格原文:https://www.cnblogs.com/ink19/p/std_thread