文章目錄
- 前言
- 正文
- 等待
- 通知
- 注意事項
- 結尾
前言
條件變量用于多線程中,其作用是在多線程間實現線程的等待、喚醒和通知機制,常配合互斥鎖(std::mutex)一起使用。它主要用于解決數據競爭問題>。
正文
條件變量只有五個函數:
方法 | 作用 |
---|---|
notify_one() | 通知一個等待的線程 |
notify_all() | 通知所有等待的線程 |
wait() | 阻塞該線程,直到條件變量被喚醒 |
wait_for() | 阻塞該線程,直到條件變量被喚醒或者到達指定時限時長后 |
wait_until() | 阻塞該線程,直到條件變量被喚醒或者到達指定時間點后 |
條件變量的方法分為兩種:通知和等待,我們一個個來說:
等待
wait()部分的就是等待函數,它接收兩個參數:
template<class Predicate>
void wait(std::unique_lock<std::mutex>& lock, Predicate pred);void wait(std::unique_lock<std::mutex>& lock);
它有兩個版本,我們先說最簡單的版本,它只有一個參數:
void wait(std::unique_lock<std::mutex>& lock);
它接收一個unique_lock作為參數。當程序運行到wait()這一行的時候,程序必定阻塞,只有等到通知之后才會繼續運行,這個狀態我們也稱之為睡眠。
那么有兩個參數的呢?它的第二個參數是一個謂詞,這里我們能夠理解為一個函數,通常是使用lambda表達式。當wait()接到通知的時候,執行這個謂詞,若是返回的結果為true,就獲取鎖的所有權,執行接下來的語句;若是為false,它就重新進入睡眠狀態,繼續阻塞線程,等待下一次通知的出現。
所以謂詞的聲明也等同于:
bool pred();
那么其他的wait就不多說了。
通知
通知有兩個函數,notify_one()和notify_all(),前者只通知一個線程,而后者則會通知所有線程,在通知之后,被通知的線程會判斷是否滿足條件函數的要求,若是符合要求,則執行其后面的函數,若是不滿足要求,則回到睡眠狀態。
注意事項
notify_one() 和 notify_all() 的調用都不會立即執行實際的喚醒操作。相反,它們只是在條件變量上設置了一個喚醒標志,并在互斥鎖釋放之后,等待其他線程重新獲取互斥鎖時才會實際執行喚醒操作。
也就是說:只有能獲取到互斥鎖的時候才會進行喚醒,并讓它去爭搶互斥鎖。
結尾
條件變量的內容其實很少,也比較好理解,問題在怎么去使用它。
這里我給出一段代碼,這段代碼是一個簡單的消息隊列,也是個非常簡單的生產者消費者隊列,實現了一個消息的發送和接收功能,配合代碼食用效果更佳(在重要的地方我都寫了注釋,希望能夠幫助大家理解):
#include <iostream>
#include <thread>
#include <memory>
#include <string>
#include <condition_variable>
#include <list>
#include <atomic>std::mutex mtx;
std::condition_variable cv;
std::list<std::string> msg;// 讀取數據
void read_thread(){while(true){std::unique_lock<std::mutex> lock(mtx);// 阻塞等待消息(并且解鎖)// 有消息再執行,沒消息不執行cv.wait(lock,[&](){ return !msg.empty(); });// 獲取到互斥鎖std::cout << "收到消息,解析中:" << std::endl;std::cout << msg.front() << std::endl;msg.pop_front();}
}// 寫入數據
void write_thread(){std::cout << "請輸入需要發送的數據:" << std::endl;std::string input;while(true){if(std::cin >> input){std::unique_lock<std::mutex> lock(mtx);msg.push_back(input);std::cout << "數據成功輸入" << std::endl;// 通知read線程,有消息可以接收cv.notify_all();}}
}int main(){std::thread write_(write_thread);write_.detach();std::thread read_(read_thread);read_.detach();// 阻塞主線程while(true);return 0;
}