条件变量
为什么需要条件变量:在多线程编程中仅使用互斥锁来完成互斥是不够用的,如以下情形:假设有两个线程 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类型的变量来表示条件变量。程序必须在使用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_init()更有效,而且在任何线程开始执行之前,确保变量被执行一次。
以下代码示例了条件变量的初始化:
pthread_cond_t cond;
int error;
if (error = pthread_cond_init(&cond, NULL))
fprintf(stderr, "Failed to initialize cond : %s\n", strerror(error));
2. 销毁条件变量函数pthread_cond_destroy()用来销毁它参数所指出的条件变量,函数原型如下:
int pthread_cond_destroy(pthread_cond_t *cond);
函数成功调用返回0,否则返回一个非0的错误码。
以下代码演示了如何销毁一个条件变量。
pthread_cond_t cond;
int error;
if (error = pthread_cond_destroy(&cond))
fprintf(stderr, "Failed to destroy cond : %s\n", strerror(error));
3.等待与通知
条件变量是与条件测试一起使用的,通常线程会对一个条件进行测试,如果条件不满足就会调用条件等待函数来等待条件满足。
条件等待函数有 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 的条件。
<span style="font-size:18px;">pthread_mutex_lock(&mutex);
while(a < b)
pthread_cond_wait(&cond, &mutex);
pthread_mutex_unlock(&mutex) ;</span>
4.当另一个线程修改了某参数可能使得条件变量所关联的条件变成真时,它应该通知一个或者多个等待在条件变量等待队列中的线程。
条件通知函数有pthread_cond_signal()和pthread_cond_broadcast()函数,
其中pthread_ cond_signal函数可以唤醒一个在条件变量等待队列等待的线程,
而pthread_cond_broadcast函数可以所有在条件变量等待队列等待的线程。
函数原型如下:
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
参数cond是一个指向条件变量的指针。函数成功返回0,否则返回一个非0的错误码。
demo6:
#include<stdio.h>
#include<string.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.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 < 10; i++)
{
pthread_mutex_lock(&sumlock);//使用互斥量保护临界变量
sum++;
printf("t%ld: read sum value = %d\n", id + 1 , sum);
pthread_mutex_unlock(&sumlock);
if (sum >= 10)
pthread_cond_signal(&cond_sum_ready);//发送条件通知,唤醒等待进程
}
return NULL;
}
void * t3(void *arg)
{
pthread_mutex_lock(&sumlock);
while(sum < 10)
pthread_cond_wait(&cond_sum_ready, &sumlock);//不满足条件一直等待
sum = 0;
printf("t3: clear sum value\n");
pthread_mutex_unlock(&sumlock);
return NULL;
}
int main(void)
{
int err;
long i;
for (i = 0; i < 2; i++)
{
//创建两个线程
err = pthread_create(&(tid[i]), NULL, &t1t2, (void *)i);
if (err != 0)
{
printf("Can't create thread :[%s]", strerror(err));
}
}
err = pthread_create(&(tid[2]), NULL, &t3, NULL);
if (err != 0)
printf("Can't create thread :[%s]", strerror(err));
for (i = 0; i < 3; i++)
pthread_join(tid[i], NULL);//阻塞等待线程退出
return 0;
}
运行效果截图: