条件变量(cond)
互斥锁有明显的缺点只有lock和unlock状态。设计多任务时容易死锁,使用也不灵活。而条件变量引入了信号弥补了互斥锁的不足。
与互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁同时使用。
条件变量使我们可以睡眠等待某种条件出现。条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:
1、等待条件:一个线程等待"条件变量的条件成立"而挂起;
2、触发条件:另一个线程使"条件成立"(给出条件成立信号)。
使用时,条件变量被用来阻塞线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化一旦其他线程改变了变量,他将通知条件变量唤醒一个或者多个线程被这个条件变量阻塞的线程。被再次获得互斥锁,然后重新判断条件是否满足。
使用条件变量之前要先进行初始化。初始化方式有两种。
1、 静态初始化:可以在单个语句中生成和初始化一个条件变量如:
pthread_cond_tmy_condition=PTHREAD_COND_INITIALIZER;(用于进程间线程的通信)。
2、 动态初始化:动态申请内存后,利用函数pthread_cond_init动态初始化。和其他动态初始化一样,在释放内存之前需要先调用pthread_cond_destroy()。
条件变量类型为 pthread_cond_t。
相关操作如下:
int pthread_cond_init(pthread_cond_t*cond, pthread_condattr_t*cond_attr);//初始化
int pthread_cond_wait(pthread_cond_t*cond, pthread_mutex_t*mutex);//等待条件成立
int pthread_cond_timewait (pthread_cond_t*cond, pthread_mutex*mutex,consttimespec* abstime); //指定等待超时时间的等待函数,
int pthread_cond_destroy(pthread_cond_t*cond); //清除条件变量:destroy
int pthread_cond_signal(pthread_cond_t*cond); //激活条件变量
int pthread_cond_broadcast(pthread_cond_t*cond);//解除所有线程的阻塞
1 初始化条件变量
使用pthread_cond_init()可以将cv所指示的条件变量初始化为其缺省值,或者指定已经使用pthread_condattr_init()设置的条件变量属性。
函数原型:
int pthread_cond_init(pthread_cond_t *cv, constpthread_condattr_t *cattr);
#include <pthread.h>
pthread_cond_t cv;
pthread_condattr_t cattr;
int ret;
/* initialize a condition variable to itsdefault value */
ret = pthread_cond_init(&cv, NULL);
/* initialize a condition variable */
ret =pthread_cond_init(&cv,&cattr);
使用PTHREAD_COND_INITIALIZER宏可以将以静态方式定义的条件变量初始化为其缺省属性。PTHREAD_COND_INITIALIZER宏与动态分配具有null属性的pthread_cond_init()等效,但是不进行错误检查。
多个线程决不能同时初始化或重新初始化同一个条件变量。如果要重新初始化或销毁某个条件变量,则应用程序必须确保该条件变量未被使用。
2 基于条件变量阻塞
使用pthread_cond_wait()可以以原子方式释放mp所指向的互斥锁,并导致调用线程基
于cv所指向的条件变量阻塞。
函数原型:
int pthread_cond_wait(pthread_cond_t*cv,pthread_mutex_t *mutex);
#include <pthread.h>
pthread_cond_t cv;
pthread_mutex_t mp;
int ret;
/* wait on condition variable */
ret = pthread_cond_wait(&cv,&mp);
阻塞的线程可以通过pthread_cond_signal()或pthread_cond_broadcast()唤醒,也可以在信号传送将其中断时唤醒。
不能通过pthread_cond_wait()的返回值来推断与条件变量相关联的条件的值的任何变化。必须重新评估此类条件。
pthread_cond_wait()例程每次返回结果时调用线程都会锁定并且拥有互斥锁,即使返回错误时也是如此。
该条件获得信号之前,该函数一直被阻塞。该函数会在被阻塞之前以原子方式释放相关的互斥锁,并在返回之前以原子方式再次获取该互斥锁。
通常,对条件表达式的评估是在互斥锁的保护下进行的。如果条件表达式为假,线程会基于条件变量阻塞。然后,当该线程更改条件值时,另一个线程会针对条件变量发出信号。这种变化会导致所有等待该条件的线程解除阻塞并尝试再次获取互斥锁。
必须重新测试导致等待的条件,然后才能从pthread_cond_wait()处继续执行。唤醒的线程重新获取互斥锁并从pthread_cond_wait()返回之前,条件可能会发生变化。等待线程可能并未真正唤醒。建议使用的测试方法是,将条件检查编写为调用pthread_cond_wait()的while()循环。
pthread_mutex_lock();
while(condition_is_false)
pthread_cond_wait();
pthread_mutex_unlock();
如果有多个线程基于该条件变量阻塞,则无法保证按特定的顺序获取互斥锁。
3 解除阻塞一个线程
对于基于cv所指向的条件变量阻塞的线程,使用pthread_cond_signal()可以解除阻塞该线程。
int pthread_cond_signal(pthread_cond_t*cv);
#include <pthread.h>
pthread_cond_t cv;
int ret;
/* one condition variable is signaled */
ret = pthread_cond_signal(&cv);
应在互斥锁的保护下修改相关条件,该互斥锁用于获得信号的条件变量中。否则,可能在条件变量的测试和pthread_cond_wait()阻塞之间修改该变量,这会导致无限期等待。调度策略可确定唤醒阻塞线程的顺序。对于SCHED_OTHER,将按优先级顺序唤醒线程。如果没有任何线程基于条件变量阻塞,则调用pthread_cond_signal()不起作用。
pthread_mutex_t count_lock;
pthread_cond_t count_nonzero;
unsigned count;
decrement_count()
{
pthread_mutex_lock(&count_lock);
while (count == 0)
pthread_cond_wait(&count_nonzero,&count_lock);
count = count - 1;
pthread_mutex_unlock(&count_lock);
}
increment_count()
{
pthread_mutex_lock(&count_lock);
if (count == 0)
pthread_cond_signal(&count_nonzero);
count = count + 1;
pthread_mutex_unlock(&count_lock);
}
4 在指定的时间之前阻塞
pthread_cond_timedwait()的用法与pthread_cond_wait()的用法基本相同,区别在于在
由abstime指定的时间之后pthread_cond_timedwait()不再被阻塞。
函数原型:
int pthread_cond_timedwait(pthread_cond_t*cv,<span style="font-family: Arial, Helvetica, sans-serif;">pthread_mutex_t *mp, const struct timespec*abstime);</span>
#include <pthread.h>
#include <time.h>
pthread_cond_t cv;
pthread_mutex_t mp;
timestruct_t abstime;
int ret;
/* wait on condition variable */
ret =pthread_cond_timedwait(&cv,&mp,&abstime);
pthread_cond_timedwait()函数会一直阻塞,直到该条件获得信号,或者最后一个参数所指
定的时间已过为止。
计时条件等待
pthread_timestruc_t to;
pthread_mutex_t m;
pthread_cond_t c;
...
pthread_mutex_lock(&m);
to.tv_sec = time(NULL) + TIMEOUT;//单位s
to.tv_nsec = 0;//单位ns
while (cond == FALSE) {
err = pthread_cond_timedwait(&c,&m, &to);
if (err == ETIMEDOUT) {
/* timeout, do something */
break;
}
}
pthread_mutex_unlock(&m);
5 在指定的时间间隔内阻塞
pthread_cond_reltimedwait_np()的用法与pthread_cond_timedwait()的用法基本相同,唯一的区别在于pthread_cond_reltimedwait_np()会采用相对时间间隔而不是将来的绝对时间作为其最后一个参数的值。
函数原型:
int pthread_cond_reltimedwait_np(pthread_cond_t*cv,pthread_mutex_t *mp,const struct timespec *reltime);
#include <pthread.h>
#include <time.h>
pthread_cond_t cv;
pthread_mutex_t mp;
timestruct_t reltime;
int ret;
/* wait on condition variable */
ret =pthread_cond_reltimedwait_np(&cv,&mp,&reltime);
pthread_cond_reltimedwait_np()每次返回时调用线程都会锁定并且拥有互斥锁,即使pthread_cond_reltimedwait_np()返回错误时也是如此。对于Solaris线程,请参见cond_reltimedwait()。pthread_cond_reltimedwait_np()函数会一直阻塞,直到该条件获得信号,或者最后一个参数指定的时间间隔已过为止。
6 解除阻塞所有线程
对于基于cv所指向的条件变量阻塞的线程,使用pthread_cond_broadcast()可以解除阻塞所有这些线程,这由pthread_cond_wait()来指定。
函数原型:
int pthread_cond_broadcast(pthread_cond_t*cv);
#include <pthread.h>
pthread_cond_t cv;
int ret;
/* all condition variables are signaled */
ret = pthread_cond_broadcast(&cv);
如果没有任何线程基于该条件变量阻塞,则调用pthread_cond_broadcast()不起作用。由于pthread_cond_broadcast()会导致所有基于该条件阻塞的线程再次争用互斥锁,因此请谨慎使用pthread_cond_broadcast()。例如,通过使用pthread_cond_broadcast(),线程可在资源释放后争用不同的资源量。
pthread_mutex_t rsrc_lock;
pthread_cond_t rsrc_add;
unsigned int resources;
get_resources(int amount)
{
pthread_mutex_lock(&rsrc_lock);
while (resources< amount) {
pthread_cond_wait(&rsrc_add, &rsrc_lock);
}
resources -=amount;
pthread_mutex_unlock(&rsrc_lock);
}
add_resources(int amount)
{
pthread_mutex_lock(&rsrc_lock);
resources +=amount;
pthread_cond_broadcast(&rsrc_add);
pthread_mutex_unlock(&rsrc_lock);
}
在add_resources()中,首先更新resources还是首先在互斥锁中调用pthread_cond_broadcast()无关紧要。应在互斥锁的保护下修改相关条件,该互斥锁用于获得信号的条件变量中。否则,可能在条件变量的测试和pthread_cond_wait()阻塞之间修改该变量,这会导致无限期等待。
7 销毁条件变量状态
使用pthread_cond_destroy()可以销毁与cv所指向的条件变量相关联的任何状态。
函数原型:
int pthread_cond_destroy(pthread_cond_t*cv);
#include <pthread.h>
pthread_cond_t cv;
int ret;
/* Condition variable is destroyed */
ret = pthread_cond_destroy(&cv);
请注意,没有释放用来存储条件变量的空间。
生产者消费者问题