参考资料
- https://en.cppreference.com/w/cpp/thread/condition_variable
- https://en.cppreference.com/w/cpp/thread/condition_variable/wait
作用
条件变量,头文件<condition_variable>。用于多个线程并发操作共享变量(即条件)时,阻塞其中的一个或者多个变量,在满足指定条件(共享变量被修改使得等待条件满足,或者阻塞超时)下唤醒这个或者这些变量。
用法
Notification线程
-
持有互斥锁,使用 下面两种形式都可以;
/************ lock_guard形式 ************/ { std::lock_guard<std::mutex> autoLock(mutex_); g_flag = true; // 修改全局变量 } cv.notify_all(); /************ unique_lock形式 ************/ std::unique_lock<std::mutex> autoLock(mutex_); g_flag = true; // 修改全局变量 autoLock.unlock(); cv.notify_all();
-
在持有锁的情况下修改共享变量;
-
释放锁后执行notify_one()或者notify_all();
-
共享变量即便是atomic类型,也要上互斥锁。
Waiting线程
-
持有互斥锁,必须使用
std::unique_lock<std::mutex> lock(mutex_)
形式; -
执行wait()或者wait_for()或者wait_until(),这一步会自动释放互斥量并且阻塞线程。这正是必须使用unique_lock的原因,因为lock_gurad没有unlock方法,只有析构了才能释放互斥量;
-
当收到通知、阻塞超时其中一种情况发生时,该线程被唤醒。此时应该再次检查条件,防止虚假唤醒,如果是虚假唤醒,应该重新阻塞当前线程并等待下次唤醒。
(比如,Notification线程发出通知,有10个Waiting线程同时被唤醒,其中1个线程获得互斥量的所有权,然后把共享变量修改掉,使得等待条件不满足。这对于其他9个线程来说,虽然被唤醒了,但是等待的条件又被那个动作最快的线程改掉了,现在条件又不满足了,这就是虚假唤醒。)
//下面这两种写法都可以防止虚假唤醒; // 方法一 cv.wait(lock, pred()); // 方法二 while (!pred()) { cv.wait(lock); }
-
std::condition_variable只和std::unique_lock搭配使用。std::condition_variable_any可以和任何锁搭配使用,比如std::shared_lock。std::condition_variable性能更优,推荐使用。
相关函数
Notification
- notify_one
- notify_all
Waiting
- wait
- wait_for
- wait_until
Demo
#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex m;
std::condition_variable cv;
std::string data;
bool ready = false;
bool processed = false;
void worker_thread()
{
// 等待直至 main() 发送数据
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, []{return ready;});
// 等待后,我们占有锁。
std::cout << "Worker thread is processing data\n";
data += " after processing";
// 发送数据回 main()
processed = true;
std::cout << "Worker thread signals data processing completed\n";
// 通知前完成手动解锁,以避免等待线程才被唤醒就阻塞(细节见 notify_one )
lk.unlock();
cv.notify_one();
}
int main()
{
std::thread worker(worker_thread);
data = "Example data";
// 发送数据到 worker 线程
{
std::lock_guard<std::mutex> lk(m);
ready = true;
std::cout << "main() signals data ready for processing\n";
}
cv.notify_one();
// 等候 worker
{
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, []{return processed;});
}
std::cout << "Back in main(), data = " << data << '\n';
worker.join();
}
最终输出:
main() signals data ready for processing
Worker thread is processing data
Worker thread signals data processing completed
Back in main(), data = Example data after processing