一 条件变量的APUE定义:
条件变量是线程的另外一种同步机制,这些同步对象为线程提供了会合的场所,理解起来就是两个(或者多个)线程需要碰头(或者说进行交互-一个线程给另外的一个或者多个线程发送消息),我们指定在条件变量这个地方发生,一个线程用于修改这个变量使其满足其它线程继续往下执行的条件,其它线程则接收条件已经发生改变的信号。
条件变量同锁一起使用使得线程可以以一种无竞争的方式等待任意条件的发生。所谓无竞争就是,条件改变这个信号会发送到所有等待这个信号的线程。而不是说一个线程接受到这个消息而其它线程就接收不到了。
二 主要函数:
pthread_cond_init()函数 功能:初始化一个条件变量
pthread_cond_wait()函数 功能:阻塞等待一个条件变量
pthread_cond_timedwait()函数 功能:限时等待一个条件变量
pthread_cond_signal()函数 功能:唤醒至少一个阻塞在条件变量上的线程
pthread_cond_broadcast()函数 功能:唤醒全部阻塞在条件变量上的线程
pthread_cond_destroy()函数 功能:销毁一个条件变量
以上6 个函数的返回值都是:成功返回0, 失败直接返回错误号。
pthread_cond_t 类型,其本质是一个结构体。为简化理解,应用时可忽略其实现细节,简单当成整数看待。如:
pthread_cond_t cond; 变量cond只有两种取值1、0。
三 函数说明:
//初始化一个条件变量
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr); //原子操作
参数2:尽管POSIX标准中为条件变量定义了属性,但在Linux中没有实现,因此cond_attr值通常为NULL,且被忽略。
也可以使用静态初始化的方法,初始化条件变量:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
//阻塞等待一个条件变量
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex); //原子操作
函数作用:
阻塞等待条件变量cond(参数1)满足
释放已掌握的互斥锁(解锁互斥量)相当于pthread_mutex_unlock(&mutex);
当被唤醒,pthread_cond_wait函数返回时,解除阻塞并重新申请获取互斥锁pthread_mutex_lock(&mutex);
//时等待一个条件变量
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
参数3: 参看man sem_timedwait函数,查看struct timespec结构体。
struct timespec {
time_t tv_sec; /* seconds */ 秒
long tv_nsec; /* nanosecondes*/ 纳秒
}
形参abstime:绝对时间。
如:time(NULL)返回的就是绝对时间。而alarm(1)是相对时间,相对当前时间定时1秒钟。
struct timespec t = {1, 0};
pthread_cond_timedwait (&cond, &mutex, &t); 只能定时到 1970年1月1日 00:00:01秒(早已经过去)
正确用法:
time_t cur = time(NULL); 获取当前时间。
struct timespec t; 定义timespec 结构体变量t
t.tv_sec = cur+1; 定时1秒
pthread_cond_timedwait (&cond, &mutex, &t); 传参 参APUE.11.6线程同步条件变量小节
//唤醒至少一个阻塞在条件变量上的线程
int pthread_cond_signal(pthread_cond_t *cond);
//唤醒全部阻塞在条件变量上的线程
int pthread_cond_broadcast(pthread_cond_t *cond);
//销毁一个条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
代码:
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
struct node
{
int n_number;
struct node *n_next;
}*head = NULL;
static void cleanup_handler(void *arg)
{
printf("Cleanup handler of second thread.\n");
free(arg);
(void)pthread_mutex_unlock(&mtx);
}
static void *thread_func(void *arg)
{
struct node *p = NULL;
pthread_cleanup_push(cleanup_handler, p);
while (1)
{
// 这个mutex主要是用来保证pthread_cond_wait的并发性。
pthread_mutex_lock(&mtx);
while (head == NULL)
{
/* 这个while要特别说明一下,单个pthread_cond_wait功能很完善,为何
* 这里要有一个while (head == NULL)呢?因为pthread_cond_wait里的线
* 程可能会被意外唤醒,如果这个时候head != NULL,则不是我们想要的情况。
* 这个时候,应该让线程继续进入pthread_cond_wait
* pthread_cond_wait会先解除之前的pthread_mutex_lock锁定的mtx,
* 然后阻塞在等待对列里休眠,直到再次被唤醒(大多数情况下是等待的条件成立
* 而被唤醒,唤醒后,该进程会先锁定先pthread_mutex_lock(&mtx);,再读取资源
* 用这个流程是比较清楚的。*/
pthread_cond_wait(&cond, &mtx);
p = head;
head = head->n_next;
printf("Got %d from front of queue\n", p->n_number);
free(p);
}
pthread_mutex_unlock(&mtx); // 临界区数据操作完毕,释放互斥锁。
}
pthread_cleanup_pop(0);
return 0;
}
int main(void)
{
pthread_t tid;
int i;
struct node *p;
/* 子线程会一直等待资源,类似生产者和消费者,但是这里的消费者可以是多个消费者,
* 而不仅仅支持普通的单个消费者,这个模型虽然简单,但是很强大。*/
pthread_create(&tid, NULL, thread_func, NULL);
sleep(1);
for (i = 0; i < 5; i++)
{
p = (struct node*)malloc(sizeof(struct node));
p->n_number = i;
pthread_mutex_lock(&mtx); // 需要操作head这个临界资源,先加锁。
p->n_next = head;
head = p;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mtx); //解锁
sleep(1);
}
printf("thread 1 wanna end the line.So cancel thread 2.\n");
/* 关于pthread_cancel,有一点额外的说明,它是从外部终止子线程,子线程会在最近的取消点,
* 退出线程,而在我们的代码里,最近的取消点肯定就是pthread_cond_wait()了。*/
pthread_cancel(tid);
pthread_join(tid, NULL);
printf("All done -- exiting\n");
return 0;
}
gcc执行结果: