条件变量
【条件变量本身不是锁!但是它也可以造成线程阻塞。通常与互斥锁配合使用。给多进程提供一个会和的场所(共享数据)】
主要应用函数
1. pthread_cond_init //初始化条件变量
2. pthread_cond_destroy //销毁条件变量
3. pthread_cond_wait //线程等待信号触发,如果没有信号触发,无限期等待下去。
4. pthread_cond_timedwait //线程等待一定的时间,如果超时或有信号触发,线程唤醒。
5. pthread_cond_signal //唤醒阻塞在该条件变量上的至少一个线程
6. pthread_cond_broadcast //唤醒阻塞在该条件变量上的所有线程
【以上6个函数的返回值都是:成功:0;失败:错误号】
- pthread_cond_t 类型 用于定义 条件变量
- pthread_cond_t cond;
pthread_cond_init 函数
【初始化一个条件变量】
int pthread_cond_init(pthread_cond_t *restrict cond, pthread_condattr *restrict attr);
【参数2】attr为条件变量属性,通常为默认值,传NULL即可
也可以使用静态初始化的方法初始化条件变量
pthread_cont_t cond = PTHREAD_COND_INITIALIZER
pthread_cond_destroy 函数
【销毁条件变量】
int pthread_cond_destroy(pthread_cond_t *cond);
pthread_cond_wait 函数
【阻塞等待一个条件变量】
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
【函数作用】
1. 阻塞等待条件变量cond(参数1)满足
2. 释放已掌握的互斥锁(解锁互斥量)相当于 pthread_mutex_unlock(&mutex);
[1,2两步为一个原子操作,意味着执行该函数之前首先得定义一个互斥量,并初始化,还要申请信号量]
3. 当被唤醒,pthread_cond_wait函数返回时,解除阻塞并重新申请获取互斥锁pthread_mutex_lock(&mutex);
pthread_cond_timedwait 函数
【限时等待一个条件变量】
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
【struct timespec 结构体】
struct timespec{
time_t tv_sec; //秒
long tv_nsec; //纳秒
};
【形参: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;
t.tv_sec = cur + 1; //定时1秒
pthread_cond_timedwait(&cond, &mutex, &t);
pthread_cond_signal 函数
【唤醒阻塞在该条件变量上的至少一个线程】
int pthread_cond_signal(pthread_cond_t *cond);
pthread_cont_broadcast 函数
【唤醒阻塞在该条件变量上的所有线程】
int pthread_cond_broadcast(pthread_cond_t *cond);
条件变量的优点
- 相较于 mutex 而言,条件变量可以减少竞争
- 如直接使用mutex,除了生产者、消费者之间要竞争互斥量以外,消费者之间也需要竞争互斥量,但如果汇聚(链表)中没有数据,消费者之间竞争互斥锁是无意义的。有了条件变量机制以后,只有生产者完成生产,才会引起消费者之间的竞争。提高了程序效率。
练习
用一个链表模拟生产者和消费者的临界区,实现消费者和生产者模型
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
//静态初始化的方法初始化 mutex 和 has_product
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;;
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;;
//定义一个链表实现生产者和消费者共享区
struct msg{
struct msg *next;
int num;
};
struct msg *head;
struct msg *node;
void *producer(void *arg)
{
while(1)
{
node = (struct msg*)malloc(sizeof(struct msg));
node->num = rand() % 100 + 1;
pthread_mutex_lock(&mutex);
node->next = head;
head = node; //头插法
printf("Produce -- %d\n", node->num);
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&has_product); //唤醒一个线程
sleep(rand() % 5);
}
return NULL;
}
void *customer(void *arg)
{
while(1)
{
pthread_mutex_lock(&mutex);
// 此处不能用if语句 if(head == NULL):消费者线程可能有多个
while(head == NULL)
{
pthread_cond_wait(&has_product, &mutex);
}
//消费链表的第一个节点
node = head;
head = node->next;
printf("Costume -- %d\n", node->num);
pthread_mutex_unlock(&mutex);
free(node); //不要忘记释放node
sleep(rand() % 5);
}
}
int main()
{
pthread_t pid, cid;
srand(time(NULL));
//动态初始化
//pthread_mutex_init(&mutex,NULL);
//pthread_cond_init(&has_product, NULL);
pthread_create(&pid, NULL, producer, NULL);
pthread_create(&cid, NULL, customer, NULL);
pthread_join(pid, NULL);
pthread_join(cid, NULL);
return 0;
}
【运行结果】因为在生产者函数中,每生产一个,就插入链表头,消费者每次都从链表头开始消费