用于阻塞一个线程,或同时阻塞多个线程,直至另一线程修改共享变量(条件)并通知 condition_variable
。
有意修改变量的线程必须
- 获得
std::mutex
(常通过 std::lock_guard ) - 在保有锁时进行修改
- 在
std::condition_variable
上执行 notify_one 或 notify_all (不需要为通知保有锁)
即使共享变量是原子的,也必须在互斥下修改它。
任何有意在 std::condition_variable
上等待的线程必须
- 在与用于保护共享变量者相同的互斥上获得 std::unique_lock<std::mutex>
- 执行下列之一:
- 检查条件,是否为已更新或提醒它的情况
- 执行 wait 、 wait_for 或 wait_until ,等待操作自动释放互斥,并悬挂线程的执行。
- condition_variable 被通知时,时限消失或虚假唤醒发生,线程被唤醒,且自动重获得互斥。之后线程应检查条件,若唤醒是虚假的,则继续等待。
一:常用成员函数
通知 | |
通知一个等待的线程(公开成员函数),有多个线程时随机通知一个线程 | |
通知所有等待的线程(公开成员函数) | |
等待 | |
阻塞当前线程,直到条件变量被唤醒(公开成员函数) | |
阻塞当前线程,直到条件变量被唤醒,或到指定时限时长后(公开成员函数) | |
阻塞当前线程,直到条件变量被唤醒,或直到抵达指定时间点(公开成员函数) |
wait:
void wait( std::unique_lock<std::mutex>& lock );
template< class Predicate >
void wait( std::unique_lock<std::mutex>& lock, Predicate pred );
包含两种重载,第一种只包含unique_lock对象,另外一个Predicate 对象(等待条件),这里必须使用 unique_lock,因为wait函数的工作原理:
- 当前线程调用wait()后将被阻塞并且函数会解锁互斥量,直到另外某个线程调用notify_one或者 notify_all唤醒当前线程;一旦当前线程获得通知(notify),wait()函数也是自动调用lock(),同理不 能使用lock_guard对象。
- 如果wait没有第二个参数,第一次调用默认条件不成立,直接解锁互斥量并阻塞到本行,直到某一 个线程调用notify_one或notify_all为止,被唤醒后,wait重新尝试获取互斥量,如果得不到,线程 会卡在这里,直到获取到互斥量,然后无条件地继续进行后面的操作。
- 如果wait包含第二个参数,如果第二个参数不满足,那么wait将解锁互斥量并堵塞到本行,直到某 一个线程调用notify_one或notify_all为止,被唤醒后,wait重新尝试获取互斥量,如果得不到,线 程会卡在这里,直到获取到互斥量,然后继续判断第二个参数,如果表达式为false,wait对互斥量解锁并堵塞到本行,直到某 一个线程调用notify_one或notify_all为止,,如果为true,则进行后面的操作。
//2) 等价于
while (!pred()) {
wait(lock);
}
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);
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
不同的是,
wait_for
可以执行一个时间段,在线程收到唤醒通知或者时间超时之前,该线程都会处于阻塞状态,如果收到唤醒通知或者时间超时,wait_for
返回,剩下操作和
wait
类似。
wait_unti:
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 );
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 );
与wait_for
类似,只是
wait_until
可以指定一个时间点,在当前线程收到通知或者指定的时间点超时之 前,该线程都会处于阻塞状态。如果超时或者收到唤醒通知,wait_until
返回,剩下操作和
wait
类似。
notity_one:
唤醒
正在等待当前条件的线程中的一个,如果没有线程在等待,则函数不执行任何操作,如果正在等待
的线程多余一个,则唤醒的线程是不确定的。
notiy_all:
唤醒正在等待当前条件的所有线程,如果没有正在等待的线程,则函数不执行任何操作。
二:lock_guard和unique_lock的使用和区别
在作用域块期间占有互斥提供便利 RAII 风格机制,创建lock_guard/unique_lock对象时,会给互斥量上锁;离开lock_guard/unique_lock作用域时,会销毁lock_guard/unique_lock,并且释放互斥。
#include <thread>
#include <mutex>
#include <iostream>
int g_i = 0;
std::mutex g_i_mutex; // 保护 g_i
void safe_increment()
{
std::lock_guard<std::mutex> lock(g_i_mutex);
++g_i;
std::cout << std::this_thread::get_id() << ": " << g_i << '\n';
// g_i_mutex 在锁离开作用域时自动释放
}
int main()
{
std::cout << "main: " << g_i << '\n';
std::thread t1(safe_increment);
std::thread t2(safe_increment);
t1.join();
t2.join();
std::cout << "main: " << g_i << '\n';
}
unique_lock,lock_guard的区别:
- unique_lock与lock_guard都能实现自动加锁和解锁,但是前者更加灵活,能实现更多的功能。
-
unique_lock 可以进行临时解锁和再上锁,如在构造对象之后使用 lck.unlock() 就可以进行解锁, lck.lock()进行上锁,而不必等到析构时自动解锁。