条件变量中有以下两类函数:
唤醒函数(直接参考链接):
- notify_one:只唤醒队列中的第一个线程
- notify_all:所有线程被一个一个唤醒,先抢到锁的先唤醒
等待函数:
- wait( std::unique_lock<std::mutex>& lock ):阻塞直到被唤醒
- wait( std::unique_lock<std::mutex>& lock, Predicate pred ):阻塞,但是被唤醒时,如果函数对象pred返回为false,则继续阻塞
生产者消费者模式的代码(以下代码参考链接):
讯享网
互斥锁mtx的作用(避免唤醒操作被错过):还未阻塞就收到唤醒操作,从而导致唤醒操作被错过。如果没有互斥锁将会出现以下情况:
wait线程判断完不满足,但在阻塞之前notify线程修改了que并执行了notify,wait线程随后才被阻塞,这样wait线程就错过了这次唤醒,这也就是所谓的Lost wakeup问题。
的作用(防止虚假唤醒):不能替换为,这是为了防止虚假唤醒。如果替换为可能会出现如下结果:
- th2[0]拿完了队列里最后一个产品正在处理,此时队列为空。
- th2[1]想去队列里拿发现已经空了,所以停在了wait上。
- th1[0]拿到mtx后,往队列添加了一个产品,并执行了notify_one通知处于等待状态的消费者。
- 由于收到了notify,th2[1]准备要被调度,但是th2[0]此时恰好处理完了手头的任务,并进行了下一轮循环,抢在th2[1]之前拿到了mtx并取走了th1[0]刚放进去的产品,此时th2[1]被阻塞,随后th2[0]释放了mtx。
- th2[0]释放了mtx后,th2[1]终于拿到了mtx却发现队列又是空的,这就是一次虚假唤醒,对于这种情况th2[1]需要继续wait。要想实现“继续wait”,就需要使用,而不是
虚假唤醒:被唤醒了,但是资源却被其他线程先抢走了
wait()的第二个形参可用于代替:
讯享网
整个同步(参考自:链接):
实际上,condition_variable变量内部也有一个mutex,用于保护等待列表的修改,假设我们定义的mutex为m_a,cv内部的mutex为m_b,那么所以整个同步过程其实是
wait线程:
获取m_a,判断condition,发现不满足。
调用wait:
(1)获取m_b,获取后notify线程的notify也会被阻塞。
(2)释放m_a。
(3)线程挂到等待列表里。 释放m_b后进行等待。
(4)被唤醒后重新获取m_a进行后续操作。
notify线程:获取m_a,修改condition。
调用notify :
(1)获取m_b 。
(2)释放m_b后,通知等待列表内的线程唤醒。
释放m_a。//这一步可以在notify之前做,也就是notify不需要hold 外部mutex
核心就是wait线程会带着外部的锁来获取等待队列锁,这把队列锁用于:wait线程push线程到等待队列和notify线程进行notify操作。而notify线程修改condition之前也需要获得外部锁,因此只要wait线程先拿到外部锁可以确保也会先拿到等待队列锁,确保了wait线程在确定需要wait但真正push到等待列表之前的这段时间notify线程没法进行notify,避免错过唤醒。

参考自:链接。
条件变量是非常底层的同步原语,很少直接使用,一般都是用它来实现高层的同步措施,如或。
倒计时(CountDownLatch)是一种常用且易用的同步手段。它主要有两种用途:
- 主线程发起多个子线程,等这些子线程各自都完成一定的任务之后,主线程才继续执行。通常用于主线程等待多个子线程完成初化。
- 主线程发起多个子线程,子线程都等待主线程,主线程完成其他一些任务之后通知所有子线程开始执行。通常用于多个子线程等待主线程发出“起跑”命令
的实现如下(参考:链接):
参考了muduo的BlockingQueue的实现,用C++11改写(参考链接):
讯享网
put函数每次添加元素都会调用notify_one(),如果更改为只在队列大小由0到1是调用notify_one(),由于生产者线程一直在生产产品,那么极端情况下队列可能一直不为空,则不会唤醒其他消费者线程消费产品,而是一直使用同一个消费者线程消费产品。这就会导致有一个消费者线程一直在消费产品,而其他的消费者线程永远无法消费产品。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/161413.html