multiple threads synchronization primitive of std::condition_variable and mutex
多线程的同步语义是多线程编程的核心,线程之间如果对数据进行同步,如何设计原语:条件变量是多线程同步的基本语义,与os操作系统的信号量语义在实际编程中比较少见。通常只用条件变量的场景比较多。
那么C++11 中的标准库已经支持std::condition_variable and mutex 互斥锁和条件变量, 所谓线程同步,就是线程之间通过某个共享的变量进行同步的操作,比如说生产者和消费者的多线程模型中。共享的buffer就是两者之间沟通的桥梁。生产者线程生产数据提供消费者线程消费。如果buffer为空的情况下。消费者需要对buffer等待。生产者一旦生成出新的数据,buffer不为空的情况下,生产者负责通知消费者。线程之间的通信就是通过mutex和condition variable机制实现的。
SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。mutex 互斥锁的目的是保护predictor这个共享的变量。以上就是buffer,一旦buffer为空,那么消费者应该进入等待buffer的状态中。以上必须用mutex保证不发生race condition,多线程编程的基础。
更复杂的是为什么wait 需要绑定mutex互斥锁呢?因为既然需要mutex保护predictor。那么消费者在操作predictor之前需要获取mutex锁。所以condition variable 在wait之前需要unlock mutex。那么为什么不能通过caller 来unlock呢?其根本目的是为了保证unlock和push 消费者线程到等待队列中这两个操作是原子的。为什么一定要原子。第一直觉就是race condition。是的,因为如果不是原子。那么在两者之间。生产者线程获得mutex之后,然后signal 唤醒信号,但是因为消费者线程没有加入等待队列。所以唤醒会丢失。这样当消费者进入睡眠等待后,会永远无法唤醒。这是严重的问题。所以在加入等待队列之前,生产者不能发送signal。
如果保证原子性呢?首先我们先说明为什么要unlock mutex锁,因为如果不unlock就wait,那么生产者永远无法得到mutex锁,因为消费者hold on mutex进入睡眠。 所以需要unlock mutex。那么一旦unlock mutex。那么生产者可以立刻得到mutex,对predictor 更新之后发送唤醒信号。这时候引入另外一个mutexB,用于wait和signal两个函数之间的序列化操作。首先lock(mutexB) 然后lock mutex,在放入等待队列之后,unlock B,然后进入睡眠。唤醒之后再次lock mutex.
本质原因还是wait 和signal之间需要同步机制,所以我们需要更外的mutexB。两者操作之前都需要获取mutexB锁。其实如果不采用mutexB,两种都依赖外部传递的mutex锁,也可以work。但是设计很奇怪,外部hold mutex之后,进入wait,我们不首先unlock mutex。而是之间加入等待队列,然后unlock mutex之后再sleep。 这时候生产者,首先要获取mutex锁,才能进行signal操作。这样signal操作也就和mutex耦合。设计有问题。尽量减少外部依赖。
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, []{return processed;});
虚假唤醒,while(predictor) 为了防止虚假唤醒。两方面原因,第一个原因就是wait的系统调用system call 被信号中断了。这时候如果需要重试,那么在判断和重试之间有race condition,此时都是无锁状态的. 即便想加锁也来不及了。判断是否需要加锁和加锁的race condition。
另外的原因就是wait被唤醒之后,要lock互斥锁。假如在此之前有其他线程抢占了mutex锁,然后更新了predictor为false。这时候再次切换回来就会虚假唤醒
