Linux用于同步的条件变量 pthread_cond_t,一开始学的时候,还是有点难理解的。这里说一下我的理解。
考虑这种情况下的读者写者:写者只往缓冲区写入数据一次,但写的时间不确定。读者负责把这个数据读出来。
利用mutex可以如下面那样实现:
写者和读者共享一个变量isWirte 但其为true时,表示已经写了。为false时,表示写者还没写。
写者:
//写者在某个不确定的时刻运行下面的代码 lock(mutex); //上锁 write(buffer);//往缓冲区写入东西 isWrite = true; //表示已经写入东西 unlock(mutex);//解锁
读者:
//由于读者不知道写者什么时候写了缓冲区,所以采用轮询这样方式 //进一步,为了不太浪费cpu,将使用sleep函数 while( !isWrite) //写者还没写入 { sleep(1); //休眠一秒钟 } lock(mutex); //上锁 read(buffer); //读取缓冲区 unlock(mutex);
明显这种实现需要轮询,而且实时性差。因为缺少一种通知的机制。如果读者在写者还没写的时候,就进入休眠状态。在写者写完后就通知读者,就可以避免轮询和消除实时性差这个问题。
为此可以设计下面的实现:
写者:
lock(mutex); //上锁 write(buffer);//往链表写入东西 unlock(mutex);//解锁 signal_to_wakeup(读者)// 发一个信号去通知读者,让读者醒来。
读者:
lock(mutex); if( !isWrite )//写者还没写入 { unlock(mutex); //解锁,让写者可以写 pause(); //进入睡眠,等待写者唤醒 } //读者已经被写者唤醒 lock(mutex); //读取临界区,需再次加锁 read(buffer); unlock(mutex);
这个实现还是比较容易理解的。但这个实现有一个竞争条件。如果读者刚执行完if语句里面的unlock(mutex).进行了解锁。还没来得及执行pause失去了cpu。而刚好,写者获得了cpu,并且执行完了上面的那些代码。它确实是发送了一个信号,唤醒读者。但此时的读者并没有进入睡眠状态。当读者再次获取cpu时,它已经错过了那个唤醒信号。所以当它执行pause,进入睡眠后。就长眠不醒了,因为写者不再发送唤醒信号了。
引起这个问题,是因为解锁和进入睡眠这两个操作由两个函数执行,不具有原子性。所以就有了pthread_cond_wait这个系统调用。用来原子地完成这个两个操作。
pthread_cond_wait
就等同于
unlock(mutex);//解锁,让写者可以写 pause(); //进入睡眠,等待写者唤醒 lock(mutex); //再次锁上,这个不要看漏了
解释到这里,大家应该懂了pthread_cond_wait的工作原理了吧。
同lock需要一个共享的mutex类型变量一样,pthread_cond_wait需要一个共享的pthread_cond_t类型变量。这里设为cond
现在用pthread_cond_wait来重新实现刚才的功能。
写者:
lock(mutex); write(buffer); unlock(mutex); pthread_cond_signal(cond);
读者:
lock(mutex); pthread_cond_wait(cond,mutex); //因为当pthread_cond_wait返回时,mutex又会被锁上,所以不要我们用//lock(mutex)加锁 read(buffer); unlock(mutex);//解锁
pthread_cond_wait也不是太难理解吧。
上面的代码,其实还是有一个缺陷。假如写者先于读者运行,并且运行了pthread_cond_signal(cond); 即发送了唤醒信号。那么将出现刚才说到的问题:当读者执行时,将长眠不醒。
解决的办法是将写者的唤醒信号保存起来,当读者执行时能找到,不会错过。
可以用一个变量来标志写者已经发送了唤醒信号这个动作。比如用一个int变量count。当count等于0时,表示写者还没发送过唤醒信号。大于0时,表示发送过唤醒信号。
实现如下:
其中共享的变量count被初始化为0
写者:
lock(mutex); write(buffer); count++; unlock(mutex); pthread_cond_signal(cond);
读者:
lock(mutex); //假如写者已经发送了信号(即count不为0),那么就不要进入睡眠了。而且此时是加锁状态,可以直接去读缓冲区 //之所以用while而不是if判断一次,是因为读者进入睡眠的时候,可能会被其他信号打断,而且过早地退出睡眠。由于不是写者唤醒的,故需要再次睡眠 while( count ==0 ) / pthread_cond_wait(cond, mutex); //因为当pthread_cond_wait返回时,mutex又会被锁上,所以不要我们lock(mutex) read(buffer); --count; //复位,可以为下次使用做处理 unlock(mutex);//解锁
很明显,条件变量可以用来模拟信号量,具体的实现可以参考我的一篇博文。