Linux线程编程 - 线程同步机制之条件变量

 前一节中我们讲述了如何使用互斥锁来实现线程间数据的共享和通信,互斥锁一个明显的缺点是它只有两种状态:锁定和非锁定。当线程A释放锁后,线程B和线程A多锁的竞争是平等的,所以会造成线程A继续抢占到锁,如果线程A一直抢占到锁,那么线程B就一直不会执行。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线程间的同步。条件变量,与锁不同, 条件变量用于等待某个条件被触发 。
  1) 大体使用的伪码: 

复制代码
// 线程一代码 
pthread_mutex_lock(&mutex); 
// 设置条件为true 
pthread_cond_signal(&cond); 
pthread_mutex_unlock(&mutex); 
// 线程二代码 
pthread_mutex_lock(&mutex); 
while (条件为false) 
pthread_cond_wait(&cond, &mutex); 
修改该条件 
pthread_mutex_unlock(&mutex); 
复制代码

需要注意几点: 

1) 
  第二段代码之所以在pthread_cond_wait外面包含一个while循环不停测试条件是否成立的原因是, 在 pthread_cond_wait被唤醒的时候可能该条件已经不成立,这个情况举例:在pthread_cond_wait解锁、测试到信号后但是在加锁前这个条件不成立了,那么通过这个While还要再检测这个条件是不是成立,那么即使收到了这样一个不稳定的错误信号,while也是跳不出去的。 UNPV2对这个的描述是:"Notice that when pthread_cond_wait returns, we always test the condition again, because spurious wakeups can occur: a wakeup when the desired condition is still not true.". 
2) 
  pthread_cond_wait调用必须和某一个mutex一起调用, 这个mutex是在外部进行加锁的mutex, 这个锁的作用是互斥,因为两个线程要对线程间共享的某个数据作操作,互斥就是必不可少的了。所以说pthread_cond_wait既进行了线程间的互斥还进行了线程间的同步。在调用pthread_cond_wait时, 内部的实现将首先将这个mutex解锁, 然后等待条件变量被唤醒, 如果没有被唤醒, 该线程将一直休眠, 也就是说, 该线程将一直阻塞在这个pthread_cond_wait调用中, 而当此线程被唤醒时, 将自动将这个mutex加锁. 
  man文档中对这部分的说明是: 
pthread_cond_wait atomically unlocks the mutex (as per pthread_unlock_mutex) and waits for the condition variable cond to be signaled. The thread execution is suspended and does not consume any CPU time until the condition variable is signaled. The mutex must be locked by the calling thread on entrance to thread_cond_wait. Before returning to the calling thread, pthread_cond_wait re-acquires mutex (as per pthread_lock_mutex). 
  也就是说pthread_cond_wait实际上可以看作是以下几个动作的合体: 
  a.解锁线程锁 
  b.等待条件为true 
  c.加锁线程锁. 

条件变量基本操作 


复制代码
/*
* =====================================================================================
*
* Filename: pthread3.c
*
* Description: A program of showing semaphore
*
* Version: 1.0
* Created: 03/11/2009 10:03:23 PM
* Revision: none
* Compiler: gcc
*
* Author: Futuredaemon (BUPT), gnuhpc@gmail.com
* Company: BUPT_UNITED
*
* =====================================================================================
*/
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
pthread_mutex_t count_lock;
pthread_cond_t count_nonzero;
unsigned count = 0;
void * decrement_count(void *arg)
{
  pthread_mutex_lock (&count_lock);
  printf("decrement_count get count_lock/n");
  while (count==0)
  {
    printf("decrement_count count == 0 /n");
    printf("decrement_count before cond_wait /n");
    pthread_cond_wait( &count_nonzero, &count_lock);//释放锁并等待count_nonzero被触发,当触发了count_nonzero就加锁,此时需要等待另一个线程解开锁才能加锁,所以会阻塞到另一个线程完成
    printf("decrement_count after cond_wait /n");
  }
  count = count -1;
  pthread_mutex_unlock (&count_lock);
}
void * increment_count(void *arg) {   pthread_mutex_lock(&count_lock);   printf("increment_count get count_lock/n");   if (count==0)   {     printf("increment_count before cond_signal/n");     pthread_cond_signal(&count_nonzero);     printf("increment_count after cond_signal/n");   }
  count
=count+1;   pthread_mutex_unlock(&count_lock); }
int main(void) {   pthread_t tid1,tid2;   pthread_mutex_init(&count_lock,NULL);   pthread_cond_init(&count_nonzero,NULL);   pthread_create(&tid1,NULL,decrement_count,NULL);   sleep(2);   pthread_create(&tid2,NULL,increment_count,NULL);   sleep(10);   pthread_exit(0); }
复制代码

  我们现在要讨论的是什么时候单一Mutex不够,还需要这么麻烦用条件变量? 
  假设有共享的资源sum,与之相关联的mutex是lock_s.假设每个线程对sum的操作很简单的,与sum的状态无关,比如只是sum++.那么只用mutex足够了.程序员只要确保每个线程操作前,取得lock,然后sum++,再unlock即可. 每个线程的代码将像这样 

add() 
{ 
  pthread_mutex_lock(lock_s); 
  sum++; 
  pthread_mutex_unlock(lock_s); 
} 

如果操作比较复杂,假设线程t0,t1,t2的操作是sum++,而线程t3则是在sum到达100的时候,打印出一条信息,并对sum清零.这种情况下, 如果只用mutex, 则t3需要一个循环,每个循环里先取得lock_s,然后检查sum的状态,如果sum>=100,则打印并清零,然后unlock.如果sum>=100,则unlock,并sleep()本线程合适的一段时间. 这个时候,t0,t1,t2的代码不变,t3的代码如下 

复制代码
print() 
{ 
  while (1) 
  { 
    pthread_mutex_lock(lock_s); 
    if(sum>=100) 
    { 
      printf(“sum reach 100!”); 
      pthread_mutex_unlock(lock_s); 
    } 
    else 
    { 
      pthread_mutex_unlock(lock_s); 
      my_thread_sleep(100); 
      return OK; 
    } 
  } 
} 
复制代码

  这种办法有两个问题 
  1) sum在大多数情况下不会到达100,那么对t3的代码来说,大多数情况下,走的是else分支,只是lock和unlock,然后sleep().这浪费了CPU处理时间. 
  2) 为了节省CPU处理时间,t3会在探测到sum没到达100的时候sleep()一段时间.这样却又带来另外一个问题,亦即t3响应速度下降.可能在sum到达200的时候,t4才会醒过来. 
  3) 这样,程序员在设置sleep()时间的时候陷入两难境地,设置得太短了节省不了资源,太长了又降低响应速度.真是难办啊! 
  这个时候,condition variable内裤外穿,从天而降,拯救了焦头烂额的你. 你首先定义一个condition variable. 

复制代码
pthread_cond_t cond_sum_ready=PTHREAD_COND_INITIALIZER; 
t0,t1,t2的代码只要后面加两行,像这样 
add() 
{ 
  pthread_mutex_lock(lock_s); 
  sum++; 
  pthread_mutex_unlock(lock_s); 
  if(sum>=100) 
    pthread_cond_signal(&cond_sum_ready); 
} 
复制代码

而t3的代码则是 

复制代码
print 
{ 
  pthread_mutex_lock(lock_s); 
  while(sum<100) 
    pthread_cond_wait(&cond_sum_ready, &lock_s); 
  printf(“sum is over 100!”); 
  sum=0; 
  pthread_mutex_unlock(lock_s); 
  return OK; 
} 
复制代码

  注意两点: 

  1) 在thread_cond_wait()之前,必须先lock相关联的mutex,因为假如目标条件未满足,pthread_cond_wait()实际上会unlock该mutex, 然后block,在目标条件满足后再重新lock该mutex, 然后返回. 
  2) 为什么是while(sum<100),而不是if(sum<100) ?这是因为在pthread_cond_signal()和pthread_cond_wait()返回之间,有时间差,假设在这个时间差内,还有另外一 
个线程t4又把sum减少到100以下了,那么t3在pthread_cond_wait()返回之后,显然应该再检查一遍sum的大小.这就是用 while的用意. 

      3)条件变量不能单独使用,必须配合互斥锁一起实现对资源的互斥访问。
  这么一说就知道什么时候要用条件变量了~就在涉及判断共同变量状态时,换句话说,也就是本节所说的要进程同步的时候用~ 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值