可能大家都使用过condition variable(之后称cv),一些博客也对cv做了介绍,但是有的说的不完全正确,甚至有误导使用者的倾向,其实最合理的使用方式是查阅文档,
如果你英语还ok的话,http://en.cppreference.com/w/cpp/thread/condition_variable 读一遍即可 完全没有必要看我写的这些砖块,但是我的blog可能思路上更贴近初学者,可以借鉴一下。
背景不过多做介绍,使用condition variable的时候都要配合mutex使用,那么mutex就是为了什么呢,为什么要用这个东西呢?
为什么要这样设计?mutex多余吗?他是用来保护什么数据呢?
ok!让我们来从零开始。
condition variable的初衷是为了wait某一个东东变成true或false,然后此线程继续进行。
按照此需求,大概的代码应该是这样:
//version 1.0
Thread 1:
cv.wait();
Thread 2:
cv.signal();
这样的话有一个很严重的问题,如果t2先执行,那么t1岂不是永远停在那里了!这样与我们最初的构想完全不符!
于是产生了下面的写法:
//version 2.0
bool signaled = false;
thread 1:
if(!signaled)
cv.wait();
thread 2:
cv.signal();
signaled = true;
看上去不错吧!但是犯了一个很白痴的错误,bool并不是thread safe的,所以
//version 2.1
bool signaled = false;
thread 1:
lock;
if(!signaled) //1
unlock;
cv.wait(); //2
thread 2:
cv.signal(); //3
lock;
signaled = true; //4
unlock;
这样我们把signaled保护起来,保证thread safe了,会有什么问题吗?
当然有问题!如果执行顺序是这样的话,3-1-4-2 那么thread 1永远wait在那里了!
欸!为什么会这样呢!让我们从长计议!一开始为了保证signal是否发出,我们增加了一个标志位来判断是否信号已经发出,如果已经发出,那么cv不需要等待。接着,我们对thread safe进行了保证,然后现在又出现了执行顺序的问题!
仔 细思考一下,就会发现,其实导致问题的原因就在于需求的原子性并没有很好的用c++表达出来,你想想,thread 1的意思翻译成汉语就是“如果没有信号没有触发,那么我就等”,thread 2的意思很明显就是“触发!”,但是触发一句话到了c++语义中就变成了两个语句,多个指令,那么你很难满足我的需求,至少在不进行原子性语句约束的情况 下你无法满足我的需求。
所以 thread 1 这段代码就是一团糟!当进行wait操作之前,你能确定signaled为false吗?答案是否定的。
或者说thread 2 这段代码 当你signal之后,你的signaled马上变成true了吗? 答案也是否定的,你如果无法保证这两个语句的原子性,那么就无法达到需求。
所以我们继续修改:
//version 3.0
bool signaled = false;
thread 1:
lock;
if(!signaled) //1
cv.wait(); //2
unlock;
thread 2:
lock;
cv.signal(); //3
signaled = true; //4
unlock;
这样就ok了!但是如果你听说过spurious wakeup(可以自行查阅一下),那么3.0依然是不够完善的,
最终的版本是:
//version 4.0
bool signaled = false;
thread 1:
lock;
while(!signaled) //1
cv.wait(); //2
unlock;
thread 2:
lock;
cv.signal(); //3
signaled = true; //4
unlock;
然后我们回到最初的问题上去,为什么需要传入一个参数mutex,答案就很显然了,是为了让cv进行unlock操作的,当cv拿回线程的权杖的时候,再把mutex lock上,保证对称性。