可能大家都使用过condition variable(之后称cv)
背景不过多做介绍,使用condition variable的时候都要配合mutex使用,那么mutex就是为了什么呢,为什么要用这个东西呢?
为什么要这样设计?mutex多余吗?他是用来保护什么数据呢?
ok!让我们来从零开始。
condition variable(之后统一叫做cv)的初衷是为了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(); //3lock; signaled = true; //4unlock;
这样我们把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(); //2unlock;thread 2:lock;cv.signal(); //3 signaled = true; //4unlock;
这样就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上,保证对称性。