1、为何引入条件变量 ?
2、条件变量 std:: condition_variable
std:: condition_variable是条件变量,实际上是个类,是一个与条件相关的类,说白了就是等待一个条件的达成。这个类是需要和互斥量来配合工作的,使用时定义一个该类对象即可。
实例代码: 线程A:循环等待一个条件满足,但若条件不满足会休眠在条件变量,并不会占用CPU。 线程B:专门往消息队列扔消息(数据),然后通知其它线程。
- ?当 std::condition_variable 对象的某个 wait 函数被调用的时候,它使用 std::unique_lock(通过 std::mutex) 来锁住当前线程。当前线程会一直被阻塞,直到另外一个线程在相同的 std::condition_variable 对象上调用了 notification 函数来唤醒当前线程;
- std::condition_variable 对象通常使用 std::unique_lock<std::mutex> 来等待,如果需要使用另外的 lockable 类型,可以使用 std::condition_variable_any 类,本文后面会讲到 std::condition_variable_any 的用法;
注意:
- std :: condition_variable 仅适用于 std::unique_lock, 此限制允许在某些平台上获得最大效率。 std :: condition_variable_any 提供可与任何BasicLockable对象一起使用的条件变量,例如std :: shared_lock。
- 修改共享变量 ready/processed 时需要锁,共享变量用于避免虚假唤醒
- ? cv.wait 第一个参数必须是 unique_lock,因为它内部会执行 unlock和lock,如果需要设置超时,使用 wait_for/wait_until
特别注意,如果不使用共享变量,当通知线程在接收线程准备接收之前发送通知,接收线程将要永远阻塞了。这里,共享变量已经置位,所以它也能避免丢失唤醒。
3、notify_one()与notify_all()
notify_one() 与 notify_all() 常用来唤醒阻塞的线程;
- notify_one():因为只唤醒等待队列中的第一个线程;不存在锁争用,所以能够立即获得锁;其余的线程不会被唤醒,需要等待再次调用notify_one()或者notify_all();
- notify_all():会唤醒所有等待队列中阻塞的线程,存在锁争用,只有一个线程能够获得锁。那其余未获取锁的线程接着会怎么样?会阻塞?还是继续尝试获得锁?答案是会继续尝试获得锁(类似于轮询),而不会再次阻塞。当持有锁的线程释放锁时,这些线程中的一个会获得锁。而其余的会接着尝试获得锁。
看下面的例子:
#include <iostream> // std::cout
#include <thread> // std::thread
#include <mutex> // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable
std::mutex mtx; // 全局互斥锁.
std::condition_variable cv; // 全局条件变量.
bool ready = false; // 全局标志位.
void do_print_id(int id)
{
std::unique_lock <std::mutex> lck(mtx);
while (!ready) // 如果标志位ready=false,!ready = true时, 则执行cv.wait(lck),当前线程被阻塞;
cv.wait(lck); /*当全局标志位ready=true 之后,不执行 cv.wait(lck),
且另一个线程有 cv.notify_all()时,线程被唤醒, 继续往下执行打印线程编号id.*/
std::cout << "thread " << id << '\n';
}
void go()
{
std::unique_lock <std::mutex> lck(mtx);
ready = true; // 设置全局标志位为 true.
cv.notify_all(); // 唤醒所有线程.
}
int main()
{
std::thread threads[10];
// spawn 10 threads:
for (int i = 0; i < 10; ++i)
threads[i] = std::thread(do_print_id, i);
std::cout << "10 threads ready to race...\n";
go(); // go!
for (auto & th:threads)
th.join();
return 0;
}
运行结果为:
0 threads ready to race...
thread 1
thread 0
thread 2
thread 3
thread 4
thread 5
thread 6
thread 7
thread 8
thread 9
输出表明所有线程被唤醒,然后依旧获得了锁。
如果将go() 中的cv.notify_all() 改为cv.notify_one() ,运行结果为:
10 threads ready to race...
thread 1
输出表明只有有一个线程被唤醒,然后该线程释放锁,这时锁已经处理非锁定状态,但是其余线程依旧处于阻塞状态。
- 当前线程调用 wait() 后将被阻塞(此时当前线程应该获得了锁(mutex),不妨设获得锁 lck),直到另外某个线程调用 notify_* 唤醒了当前线程,该函数会自动调用?
lck.unlock() 释放锁,使得其他被阻塞在锁竞争上的线程得以继续执行。 - 所以,线程阻塞在condition_variable时,它是等待notify_one()或者notify_all()来唤醒,而不是等待锁可以被锁定来唤醒。线程被唤醒后,会通过轮询方式获得锁,获得锁前也一直处理运行状态,不会被再次阻塞。
参考资料
std::condition_variable why do I need std::condition_variable?
https://blog.csdn.net/guotianqing/article/details/104017649?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_title~default-0.control&spm=1001.2101.3001.4242
https://blog.csdn.net/weixin_44517656/article/details/112096777?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_title~default-0.control&spm=1001.2101.3001.4242
https://blog.csdn.net/qq_38210354/article/details/107168532?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EOPENSEARCH%7Edefault-1.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EOPENSEARCH%7Edefault-1.control
|