1、条件变量概述:
条件变量是用来等待线程而不是上锁的,条件变量通常和互斥锁一起使用。条件变量之所以要和互斥锁一起使用,主要是因为互斥锁的一个明显的特点就是它只有两种状态:锁定和非锁定,而条件变量可以通过允许线程阻塞和等待另一个线程发送信号来弥补互斥锁的不足,所以互斥锁和条件变量通常一起使用。
当条件满足的时候,线程通常解锁并等待该条件发生变化,一旦另一个线程修改了环境变量,就会通知相应的环境变量唤醒一个或者多个被这个条件变量阻塞的线程。这些被唤醒的线程将重新上锁,并测试条件是否满足。一般来说条件变量被用于线程间的同步;当条件不满足的时候,允许其中的一个执行流挂起和等待。
2、主要应用函数:
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。
3、函数分析
<1>、初始化一个条件变量
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
参2:attr表条件变量属性,通常为默认值,传NULL即可
也可以使用静态初始化的方法,初始化条件变量:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
<2>、阻塞等待一个条件变量
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
函数作用:
阻塞等待条件变量cond(参1)满足
释放已掌握的互斥锁(解锁互斥量)相当于pthread_mutex_unlock(&mutex);
1.2.两步为一个原子操作。
3.当被唤醒,pthread_cond_wait函数返回时,解除阻塞并重新申请获取互斥锁pthread_mutex_lock(&mutex);
<3>、限时等待一个条件变量
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线程同步条件变量小节
<4>、唤醒至少一个阻塞在条件变量上的线程
int pthread_cond_signal(pthread_cond_t *cond);
<5>、唤醒全部阻塞在条件变量上的线程
int pthread_cond_broadcast(pthread_cond_t *cond);
<6>、销毁一个条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
4、条件变量示例
生产者消费者条件变量模型
线程同步典型的案例即为生产者消费者模型,而借助条件变量来实现这一模型,是比较常见的一种方法。假定有两个线程,一个模拟生产者行为,一个模拟消费者行为。两个线程同时操作一个共享资源(一般称之为汇聚),生产向其中添加产品,消费者从中消费掉产品。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
//节点结构体
struct msg
{
int num; //数据区
struct msg *next; //链表区
};
struct msg *head = NULL;//头指针
struct msg *mp = NULL; //节点指针
//利用宏定义的方式初始化全局的互斥锁和条件变量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
void *producter(void *arg)
{
while (1)
{
mp = malloc(sizeof(struct msg));
mp->num = rand() % 400 + 1;
printf("---producted---%d\n", mp->num);
pthread_mutex_lock(&mutex);//访问共享区域必须加锁
mp->next = head;
head = mp;
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&has_product);//通知消费者来消费
sleep(rand() % 3);
}
return NULL;
}
void *consumer(void *arg)
{
while (1)
{
pthread_mutex_lock(&mutex);//访问共享区域必须加锁
while (head == NULL)//如果共享区域没有数据,则解锁并等待条件变量
{
pthread_cond_wait(&has_product, &mutex);
}
mp = head;
head = mp->next;
pthread_mutex_unlock(&mutex);
printf("------------------consumer--%d\n", mp->num);
free(mp); //释放被删除的节点内存
mp = NULL;//并将删除的节点指针指向NULL,防止野指针
sleep(rand() % 3);
}
return NULL;
}
int main(void)
{
pthread_t ptid, ctid;
//创建生产者和消费者线程
pthread_create(&ptid, NULL, producter, NULL);
pthread_create(&ctid, NULL, consumer, NULL);
//主线程回收两个子线程
pthread_join(ptid, NULL);
pthread_join(ctid, NULL);
return 0;
}
5、运行结果
6、条件变量的优点:
相较于mutex而言,条件变量可以减少竞争。
如直接使用mutex,除了生产者、消费者之间要竞争互斥量以外,消费者之间也需要竞争互斥量,但如果汇聚(链表)中没有数据,消费者之间竞争互斥锁是无意义的。有了条件变量机制以后,只有生产者完成生产,才会引起消费者之间的竞争。提高了程序效率。