上一篇讲了互斥量是用来防止多个线程同时访问同一共享变量,本篇讲多线程同步常用的另一个机制:条件变量。
条件变量的作用,是允许一个线程将某个共享变量的状态变化通知其他线程。其他线程会等待这一通知,等待条件变量的线程将处于阻塞状态,直到信号到来,才会唤醒并执行后续操作。
与互斥量类似,条件变量也分为静态分配的条件变量和动态分配的条件变量。
对于静态分配的条件变量,在定义时对其进行初始化如下:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
对于动态分配的条件变量,或者静态分配的条件变量,但未使用默认属性对其初始化时,必须使用pthread_cond_init():
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr); //Return 0 on success, or a positive error number on error
参数cond为要进行初始化的条件变量,attr为属性对象,可由多个Pthreads函数对其进行初始化,若将attr置为NULL,则使用一组缺省属性来设置条件变量。与互斥量类似,当不再需要动态分配的条件变量时,需要使用pthread_cond_destroy()对其进行销毁:
#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);
条件变量的主要操作是发送信号(signal)和等待(wait)。发送信号操作即通知一个或多个处于等待状态的线程,某个共享变量的状态已改变。等待操作是指在收到一个通知前,线程一直处于阻塞状态。常用函数定义如下:
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cont_t *cond);
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
发送信号有两种常用操作有pthread_cond_signal()和pthread_cond_broadcast(),两个函数均可针对由参数cond指定的条件变量而发送信号,不同之处在于,pthread_cond_signal()只保证唤醒至少一条遭到阻塞的线程,而pthread_cond_broadcast()则会唤醒所有被阻塞的线程。pthread_cond_wait()将阻塞某一线程,直到收到条件变量cond的通知。
pthread_cond_wait()的一个变体是pthread_cond_timedwait():
#include <pthread.h>
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);
pthread_cond_timedwait()通过一个参数abstime来指定一个线程等待条件变量通知时休眠时间的上限。如果abstime指定的时间间隔到期且无相关条件变量通知,则返回ETIMEOUT错误。
条件变量总会结合互斥量使用。条件变量对共享变量的状态改变发出通知,而互斥量则提供对该共享变量访问的保护。
pthread_cond_wait()在使用中,一般会置于while循环中。如下例,
pthread_mutex_lock(mutex);
while(/* Shared variable is not in the state we want */)
{
pthread_cont_wait(&cond, *mutex);
}
pthread_mutex_unlock(&mutex);
共享变量的访问必须置于互斥量的保护下。共享变量和条件变量的使用步骤一般如下:
1. 线程在准备检查共享变量的状态时锁定互斥量;
2. 检查共享变量的状态;
3. 如果共享变量未满足预设条件,线程应在等待条件变量并进入休眠前解锁互斥量(以便其他线程能访问该共享变量);
4. 当线程被条件变量的通知而被再度唤醒时,必须对互斥量再次加锁,以便立即访问共享变量。
5. 对共享变量的访问完成后,若满足条件,则解锁,并执行线程后续操作。
看一个简单的实例,线程1负责对共享变量cnt累加,并在共享变量cnt累加超过5时,发送一个信号并退出;线程2在共享变量超过5时被唤醒:
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
static int cnt = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
void *func1(void *arg)
{
for(int i = 0; i < 40000; i++)
{
pthread_mutex_lock(&mutex);
cnt++;
printf("Thread 1, cnt = %d \n", cnt);
pthread_mutex_unlock(&mutex);
sleep(1);
if(cnt > 5)
{
pthread_cond_signal(&cond);
printf("Thread 1 sent a signal. \n");
break;
}
}
return NULL;
}
void *func2(void *arg)
{
pthread_mutex_lock(&mutex);
while(cnt <= 5)
{
pthread_cond_wait(&cond, &mutex);
}
pthread_mutex_unlock(&mutex);
printf("Thread 2 is waked up.\n");
return NULL;
}
int main()
{
pthread_t myThread_1, myThread_2;
int ret;
ret = pthread_create(&myThread_1, NULL, func1, NULL);
if(ret != 0)
{
printf("Error create myThread_1! \n");
exit(1);
}
ret = pthread_create(&myThread_2, NULL, func2, NULL);
if(ret != 0)
{
printf("Error create myThread_2! \n");
exit(1);
}
ret = pthread_join(myThread_1, NULL);
if(ret != 0)
{
printf("Error join myThread_1! \n");
exit(1);
}
ret = pthread_join(myThread_2, NULL);
if(ret != 0)
{
printf("Error join myThread_2! \n");
exit(1);
}
return 0;
}
执行结果如下: