您的位置:首页 > 运维架构 > Linux

Linux 条件变量 pthread_cond_wait

2013-12-26 21:40 351 查看
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);//解锁


很明显,条件变量可以用来模拟信号量,具体的实现可以参考我的一篇博文
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: