条件变量

来源:互联网 发布:程序员接私活怎么收费 编辑:程序博客网 时间:2024/05/23 19:13
上一点提到,互斥锁不能用于持续时间不确定的情况,对于这种情况,可以通过条件变量与互斥锁联合使用来实现。例如,一个进程中有一个全局变量a,那么它被多个线程共享,其中有一个线程是要等到a为零的时候把a++,如何实现呢?
一个不正确的办法是:
while(a!=0);
a++;
上面程序虽然能解决问题,但这是一种忙等,而不是将线程挂起等待,这样的忙等会浪费处理器资源,更麻烦的是,根据线程的调度方式,这个线程将有可能永远的占用分配给这整个进程的时间片,这样整个进程就出现死等。
正确的方法是:
先定义和初始化一个互斥锁,用来同步全局变量a;
锁定互斥锁;
测试a是否为0;
如果是,a++,然后解锁;
如果不是,那么将线程挂起,并解除对互斥锁的锁定;

条件变量正是用于这种情况下的机制,下面先列出条件变量相关的函数和数据类型。
互斥锁的数据类型是:
pthread_cond_t;
互斥锁的属性数据类型是:pthread_condattr_t;
(1) 初始化函数:
int pthread_cond_init(pthread_cond_t *cond, constpthread_condattr_t *attr);

  
按参数 attr 指定的属性创建一个新的条件变量。如果参数 attr是NULL,则使用默认的属性创建条件变量。这是对动态分配的互斥锁的初始化办法,对于静态分配的互斥锁可以直接赋值初始化:
pthread_cond_tcond=PTHREAD_COND_INITIALIZER;
任何一个条件变量在被使用前,都必须初始化,且只能初始化一次(如果是被销毁了的条件变量,就可以重新初始化)。
(2) 销毁函数:
int pthread_cond_destroy(pthread_cond_t*cond);
作用是释放一个条件变量分配的资源。任何条件变量在销毁前,必须要确定没有线程正在等待它。
(3) 等待条件变量函数:
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t*mutex);

  
该函数的作用是等待一个事件(条件变量)通知,例如上面的例子,在测试a是否为0后,如果不是为0,那么就调用该函数,将线程挂起,等待对应条件变量事件的通知(被阻塞线程直到有其他线程调用pthread_cond_signal或pthread_cond_broadcast函数置相应的条件变量时才被唤醒)。这些因为等待某个特定条件变量的线程组成一个队列,这个队列与这个特定的条件变量直接相关。
int pthread_cond_timedwait(pthread_cond_t *cond,pthread_mutex_t *mutex,const struct timespec*abstime);
  
该函数比上一个函数多了一个时间参数,其作用是在一段指定时间内等待一个事件的发生,如果在指定的时间内没有被唤醒,那么函数就返回ETIMEDOUT(110)值,表示超时。这个非常有用,可以用来指定线程睡眠时间,值得注意的是,这个时间参数是绝对时间,而不是时间间隔。
(4)通知条件变量函数:
int pthread_cond_broadcast(pthread_cond_t*cond);

  
该函数用来对所有等待这个条件变量的线程解除阻塞。
int pthread_cond_signal(pthread_cond_t*cond);
  
该函数用来解除一个等待指定事件的线程的阻塞状态,如果有若干线程挂起等待该条件变量,该调用只唤起一个线程,被唤起的线程并不确定。

我们现在再回到刚才的那个问题,它的正确解决办法的程序表达如下:
int a=0; //全局变量
pthread_cond_tcond=PTHREAD_COND_INITIALIZER;
//全局变量
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER; //全局变量


   
pthread_mutex_lock(&mutex);
while(a!=0) 
//section1
{
 pthread_cond_wait(&cond,&mutex); //section2
}
a++;
pthread_cond_signal(&cond); //section3
pthread_mutex_unlock(&mutex);

看完这段程序后,大家应该有一个困惑,那就是,在section2中,好像线程没有释放互斥锁就挂起了,这样别的线程就不可能有机会获取互斥锁,所以程序将进入死锁。事实上,pthread_cond_wait和pthread_cond_timedwait函数,在把线程挂起前就把隐式的把参数中的互斥锁解锁了。对于section3,因为这个条件变量关联的变量a发生了变化,所以必须通知其他线程,所以有此段。如果a只被读取没被修改,那么就可以省去section3
另一个值得注意的问题是,当特定的条件变量对应的等待队列中的所有线程被唤醒后,并不是所有都可以执行,事实上只有一个可以运行,因为当这些线程被唤醒后,它们将试图获取对应的互斥锁,所以,只有获取到互斥锁的线程才能运行,其他线程就从这个条件等待队列转移到了对应互斥锁的等待队列中去。这样,当一个条件变量对应多个互斥锁时,将会出现等待线程的根据不同的互斥锁进行分离。
再一个就是,条件变量没有被链接到特定的断言上去,当一个条件变量与多个断言相关联的时候,线程被唤醒后就必须从新测试它所要的断言。所以,上面的代码中的section1是一个循环语句,当section2语句返回时,就会再次执行section1来判断断言。如果把section1改为if语句,那么就有可能被假唤醒。

条件变量机制的还有一个非常重要但却经常被忽视的应用。它可以让线程睡眠特定时间。因为在线程中直接用sleep函数【unsignedsleep(unsignedseconds)】很有可能会让整个进程睡眠,这不是我们所要的。另一个睡眠函数就是anonsleep函数【intnanosleep(const struct timespec *rqtp, struct timespec*rmtp)】,它可以让某个线程睡眠指定时间,可惜的是,这个函数并不是所有的系统都有提供。

http://blog.sina.com.cn/s/blog_53a204a90100075n.html
原创粉丝点击