c++ 条件变量使用详解
std::condition_variable
- 线程间同步的条件变量类。允许一个或多个线程在满足特定条件之前等待,同时允许其他线程通知、唤醒等待的线程。
成员函数
notify_one
:通知一个等待的线程。notify_all
:通知所有等待的线程。wait
:阻塞当前线程,直到条件变量被唤醒。wait_until
:阻塞当前线程,直到条件变量被唤醒,或直到抵达指定时间点。wait_for
:阻塞当前线程,直到条件变量被唤醒,或直到指定时限时长后。
一般使用方法
- 对于修改条件后通知其他线程的线程:
- 获得 std::mutex,通常使用 std::lock_guard;
- 在成功获得锁后,修改条件;
- 使用 notify_one 或 notify_all 唤醒等待的线程;
- 对于等待唤醒的线程:
- 获得 std::mutex,通常使用 std::unique_lock;
- 使用 wait,wait_for,wait_until 等待唤醒,此时线程释放互斥,并进入阻塞;
- 被其他线程唤醒,被虚假唤醒或者等待超时,线程自动重获得互斥,此时应检查条件是否成立,若不成立,则继续等待或者返回;
虚假唤醒
- 指线程在等待期间,重新获得互斥,而这一行为却不来源于其他任何线程的通知,则称之为虚假唤醒。
- 按照 c++ 标准规定,虚假唤醒出现的数量以及比率都不确定。
notify_one notify_all 使用注意
- 在唤醒其他线程时,通知线程一般无需持有互斥,因为被唤醒线程醒来后,一旦不能立即获得互斥,将会再次阻塞。
wait 函数简介
-
函数原型:
void wait(std::unique_lock<std::mutex>& lock); // 若发生虚假唤醒,wait 之后的代码将会被执行 template<class Predicate> void wait(std::unique_lock<std::mutex>& lock, Predicate pred); /** 相当于 while (!pred()) { 通过检查 pred 返回值,可以避免虚假唤醒 wait(lock); } **/
lock
:互斥体,必须被当前线程锁定;pred
:返回 false,则继续等待。其签名为bool pred()
;
-
使当前线程阻塞直至条件变量被通知,或虚假唤醒发生,可选地循环直至满足 pred。
wait_until 函数简介
-
函数原型:
template<class Clock, class Duration> std::cv_status wait_until(std::unique_lock<std::mutex>& lock, const std::chrono::time_point<Clock, Duration>& timeout_time); // 被其他线程唤醒或发生虚假唤醒,未超时,返回 std::cv_status::no_timeout // 超时则返回 std::cv_status::timeout template<class Clock, class Duration, class Pred> bool wait_until(std::unique_lock<std::mutex>& lock, const std::chrono::time_point<Clock, Duration>& timeout_time, Pred pred); /** 相当于 while (!pred()) { 通过检查 pred 返回值,可以避免虚假唤醒 if (wait_until(lock, timeout_time) == std::cv_status::timeout) { return pred(); } } return true; **/
lock
:互斥体,必须被当前线程锁定;timeout_time
:表示停止等待时间的 std::chrono::time_point 类型对象;pred
:返回 false,则继续等待。其签名为bool pred()
;
-
使当前线程阻塞直至条件变量被通知、抵达指定时间或虚假唤醒发生,可选的循环直至满足 pred。
-
使用注意:时钟最好使用稳定时钟,即计时速率恒定且无法调整的时钟。
wait_for 函数简介
-
函数原型:
template<class Rep, class Period> std::cv_status wait_for(std::unique_lock<std::mutex>& lock, const std::chrono::duration<Rep, Period>& rel_time); // 被其他线程唤醒或发生虚假唤醒,未超时,返回 std::cv_status::no_timeout, // 超时则返回 std::cv_status::timeout template<class Rep, class Period, class Predicate> bool wait_for(std::unique_lock<std::mutex>& lock, const std::chrono::duration<Rep, Period>& rel_time, Predicate pred); // 相当于 // wait_until(lock, std::chrono::steady_clock::now() + rel_time, std::move(pred));
lock
:互斥体,必须被当前线程锁定;rel_time
:表示等待所耗的最大时间的 std::chrono::duration 类型对象。rel_time 必须足够小,以在加到 std::chrono::steady_clock::now() 时不溢出;pred
:返回 false,则继续等待。其签名为bool pred()
;
-
使当前线程阻塞直至条件变量被通知、抵达指定时间或虚假唤醒发生,可选的循环直至满足 pred。
-
由于操作系统调度或资源争议,此函数可能阻塞长于 rel_time。
示例代码
-
线程1修改变量,线程2等待变量被修改。
#include <iostream> #include <condition_variable> #include <thread> #include <chrono> using namespace std::chrono_literals; int i = 0; bool changed{false}; std::condition_variable cv; std::mutex cv_m; void wait() { std::unique_lock<std::mutex> lk(cv_m); // std::chrono_literals::100ms, c++14 引入 if (cv.wait_for(lk, 100ms, [](){return changed;})) { printf("finished waiting, i is %d\n", i); } else { printf("wait timeout, i is %d\n", i); } } void notify() { { std::this_thread::sleep_for(50ms); std::lock_guard<std::mutex> lk(cv_m); i = 10; changed = true; printf("change finished\n"); } cv.notify_one(); } int main() { std::thread t1(notify); std::thread t2(wait); t1.join(); t2.join(); return 0; }