为什么需要条件变量
在多线程编程中仅使用互斥锁来完成互斥是不够用的,如以下情形:
假设有两个线程 t1 和 t2, 需要这个两个线程循环对一个共享变量 sum 进行自增操作,那么 t1 和 t2 只需要使用互斥量即可保证操作正确完成,线程执行代码如所示:
pthread_mutex_t sumlock=PTHREAD_MUTEX-INITIALIZER;
void *t1t2(void)
{
pthread_mutex_lock(&sumlock);
sum++;
pthread_mutex_unlock(&sumlock);
}
如果这时需要增加另一个线程 t3,需要 t3 在 count 大于 100 时将 count 值重新置 0 值,那么可以 t3 可以实现如下:
void *t3(void)
{
pthread_mutex_lock(&sumlock);
if(sum>=100)
{
sum=0;
pthread_mutex_unlock(&sumlock);
}else
{
pthread_mutex_unlock(&sumlock);
usleep(100);
}
}
以上代码存在以下问题:
1) sum 在大多数情况下不会到达 100, 那么对 t3 的代码来说,大多数情况下, 走的是 else分支, 只是 lock 和 unlock,然后sleep()。 这浪费了 CPU 处理时间。
2) 为了节省 CPU 处理时间, t3 会在探测到 sum 没到达 100 的时候 usleep()一段时间。这样却又带来另外一个问题, 亦即 t3 响应速度下降。 可能在 sum 到达 200 的时候, t3 才会醒过来。
这样时间与效率出现了矛盾,而条件变量就是解决这个问题的好方法。
创建与销毁
1.创建条件变量
pthreads用pthread_cond_t类型的变量来表示条件变量,要先进行初始化
(1)静态初始化:对于静态分配的变量可以简单地将 PTHREAD_COND_INITIALIZER 赋值给变量来初始化默认行为的条件变量。
pthread_cond_t cond=PTHREAD_COND_INITIALIZER
(2)动态初始化:对动态分配或者不使用默认属性的条件变量来说可以使用 pthread _cond_init()来初始化。函数原型如下:
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
参数 cond 是一个指向需要初始化 pthread_cond_t 变量的指针,参数 attr 传递 NULL 值时, pthread_cond_init()将 cond 初始化为默认属性的条件变量。函数成功将返回 0;否则返回一个非 0 的错误码。以下为代码示例:
pthread_cond_t cond;
int err;
if(err=pthread_cond_init(&cond,NULL))
fprintf(stderr,"Failed to initialize cond:%s\n",strerror(err));
2.销毁条件变量
函数 pthread_cond_destroy()用来销毁它参数所指出的条件变量,函数原型如下:
int pthread_cond_destroy(pthread_cond_t *cond);
函数成功调用返回 0,否则返回一个非 0 的错误码。以下为代码示例:
pthread_cond_t cond;
int err;
if(err=pthread_cond_destroy(&cond));
fprintf(stderr,"Failed to destroy cond:%s\n",stderror(err));
等待与通知
1.等待
条件变量是与条件测试一起使用的,通常线程会对一个条件进行测试,如果条件不满足就会调用条件等待函数来等待条件满足。
条件等待函数有 pthread_cond_wait()pthread_cond_timedwait()和两个,函数原型如下:
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex,
const struct timespec *restrict abstime);
pthread_cond_wait()函数在条件不满足时将一直等待, 而 pthread_cond_timedwait()将只
等待一段时间。
参数 cond 是一个指向条件变量的指针,参数 mutex 是一个指向互斥量的指针,线程在调用前应该拥有这个互斥量,当线程要加入条件变量的等待队列时,等待操作会使线程释放这个互斥量。 pthread_timedwait()的第三个参数 abstime 是一个指向返回时间的指针,如果条件变量通知信号没有在此等待时间之前出现,等待将超时退出, abstime 是个绝对时间,而不是时间间隔。
以上函数成功调用返回 0,否则返回非 0 的错误码,其中 pthread_cond_timedwait() 函数如果 abstime 指定的时间到期,错误码为 ETIMEOUT。
以下代码使得线程进入等待,直到收到通知并且满足 a 大于等于 b 的条件。
pthread_mutex_lock(&mutex);
while(a<b)
{
pthread_cond_wait(&cond,&mutex);
}
pthread_mutex_unlock(&mutex);
2.通知
当另一个线程修改了某参数可能使得条件变量所关联的条件变成真时,它应该通知一个或者多个等待在条件变量等待队列中的线程。
条件通知函数有 pthread_cond_signal()和 pthread_cond_broadcast()函数,其中 pthread_cond_signal 函数可以唤醒一个在条件变量等待队列等待的线程,而 pthread_cond_broadcast函数可以唤醒所有在条件变量等待队列等待的线程。函数原型如下:
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
pthread_t tid[3];
int sum=0;
pthread_mutex_t sumlock=PTHREAD_MUTEX_INITIALIZER; /*静态初始化互斥量*/
pthread_cond_t cond_sum_ready=PTHREAD_COND_INITIALIZER; /*静态初始化条件变量*/
void *t1t2(void *arg)
{
int i;
long id=(long)arg;
for(i=0;i<60;i++)
{
pthread_mutex_lock(&sumlock); /*使用互斥量保护临界变量*/
sum++;
printf("t%ld:read sum value=%d\n",id+1,sum);
pthread_mutex_unlock(&sumlock);
if(sum>=100)
pthread_cond_signal(&cond_sum_ready);
}
return NULL;
}
void *t3(void *arg)
{
pthread_mutex_lock(&sumlock);
while(sum<100)
{
pthread_cond_wait(&cond_sum_ready,&sumlock);
}
sum=0;
printf("t3:clear sum value\n");
pthread_mutex_unlock(&sumlock);
return NULL;
}
int main(int argc, char *argv[])
{
int err;
long i;
for(i=0;i<2;i++)
{
err=pthread_create(&tid[i],NULL,&t1t2,(void *)i); /*创建线程1线程2*/
if(err!=0)
printf("Can't create thread:%s\n",strerror(err));
}
err=pthread_create(&tid[2],NULL,&t3,NULL); /*创建线程3*/
if(err!=0)
printf("Can't create thread:%s\n",strerror(err));
for(i=0;i<3;i++)
{
pthread_join(tid[i],NULL);
}
return 0;
}
运行结果如图所示。尽管程序会在 sum 累加到 100 时发送条件通知,但正如图中红框出所示,当 sum 计算到 120 时 t3 才可能被调用,这是因为 signal 与 wait两个调用之间存在可能的间隙