std::thread執行緒庫詳解(4)
阿新 • • 發佈:2021-02-07
## 目錄
- [目錄](#目錄)
- [前言](#前言)
- [條件變數](#條件變數)
- [一些需要注意的地方](#一些需要注意的地方)
- [總結](#總結)
## 前言
本文主要介紹了多執行緒中的條件變數,條件變數在多執行緒同步中用的也比較多。我第一次接觸到條件變數的時候是在完成一個多執行緒佇列的時候。條件變數用在佇列沒有資料時,等待入隊執行緒入隊資料。相比較於鎖的使用,條件變數的使用更為複雜,使用時需要注意的部分也更多。本文將會完成一個阻塞佇列(對普通佇列進行一個簡單的包裝),以此來完成條件變數的介紹。
## 條件變數
條件變數(`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