条件变量:
条件变量本身不是锁!但它也可以造成线程阻塞。通常与互斥锁配合使用。给多线程提供一个会合的场所。
• 与互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁同时使用。
• 条件变量使我们可以睡眠等待某种条件出现。条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。
• 条 件的检测是在互斥锁的保护下进行的。如果一个条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。如果另一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多个等待它的线程,重新获得互斥锁,重新评价条件。如果两进程共享可读写的内存,条件变量可以被用来实现这两进程间的线程同步。
条件变量使用规范
等待条件代码
pthread_mutex_lock(&mutex);
while (条件为假)
pthread_cond_wait(cond, mutex);
执行任务+修改条件(为假)
pthread_mutex_unlock(&mutex);
给条件发送信号代码
pthread_mutex_lock(&mutex);
执行任务+修改条件(为真)
pthread_cond_signal(cond);
pthread_mutex_unlock(&mutex);
• 使用条件变量之前要先进行初始化。可以在单个语句中生成和初始化一个条件变量如:
主要应用函数:
pthread_cond_init函数
pthread_cond_destroy函数
pthread_cond_wait函数
pthread_cond_timedwait函数
pthread_cond_signal函数
pthread_cond_broadcast函数
以上6 个函数的返回值都是:成功返回0, 失败直接返回错误号。
pthread_cond_t类型 用于定义条件变量
pthread_cond_t cond;
pthread_cond_init函数
初始化一个条件变量
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;
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);
函数作用:
-
阻塞等待条件变量cond(参1)满足
-
释放已掌握的互斥锁(解锁互斥量)相当于pthread_mutex_unlock(&mutex);
1.2.两步为一个原子操作。
-
当被唤醒,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);
参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线程同步条件变量小节
在讲解setitimer函数时我们还提到另外一种时间类型:
struct timeval {
time_t tv_sec; /* seconds */ 秒
suseconds_t tv_usec; /* microseconds */ 微秒
};
pthread_cond_signal函数
唤醒至少一个阻塞在条件变量上的线程
int pthread_cond_signal(pthread_cond_t *cond);
pthread_cond_broadcast函数
唤醒全部阻塞在条件变量上的线程
int pthread_cond_broadcast(pthread_cond_t *cond);
生产者消费者条件变量模型
线程同步典型的案例即为生产者消费者模型,而借助条件变量来实现这一模型,是比较常见的一种方法。假定有两个线程,一个模拟生产者行为,一个模拟消费者行为。两个线程同时操作一个共享资源(一般称之为汇聚),生产向其中添加产品,消费者从中消费掉产品。
看如下示例,使用条件变量模拟生产者、消费者问题:
/*借助条件变量模拟 生产者-消费者 问题*/
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>
/*作为公享数据,需被互斥量保护*/
struct msg {
struct msg *next;
int num;
};
struct msg *head;
struct msg *mp;
/* 静态初始化 一个条件变量 和 一个互斥量*/
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
void *consumer(void *p)
{
for (;;) {
pthread_mutex_lock(&lock);
while (head == NULL) { //头指针为空,说明没有节点 可以为if吗
pthread_cond_wait(&has_product, &lock);
}
mp = head;
head = mp->next; //模拟消费掉一个产品
pthread_mutex_unlock(&lock);
printf("-Consume ---%d\n", mp->num);
free(mp);
mp = NULL;
sleep(rand() % 5);
}
}
void *producer(void *p)
{
for (;;) {
mp = malloc(sizeof(struct msg));
mp->num = rand() % 1000 + 1; //模拟生产一个产品
printf("-Produce ---%d\n", mp->num);
pthread_mutex_lock(&lock);
mp->next = head;
head = mp;
pthread_mutex_unlock(&lock);
pthread_cond_signal(&has_product); //将等待在该条件变量上的一个线程唤醒
sleep(rand() % 5);
}
}
int main(int argc, char *argv[])
{
pthread_t pid, cid;
srand(time(NULL));
pthread_create(&pid, NULL, producer, NULL);
pthread_create(&cid, NULL, consumer, NULL);
pthread_join(pid, NULL);
pthread_join(cid, NULL);
return 0;
}
另一个版本的生产着消费者模型
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <pthread.h>
int g_Count = 0;
int nNum, nLoop;
//定义锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//定义条件并初始化
pthread_cond_t my_condition=PTHREAD_COND_INITIALIZER;
#define CUSTOM_COUNT 2
#define PRODUCT_COUNT 4
// int pthread_mutex_lock(pthread_mutex_t *mutex);
// int pthread_mutex_trylock(pthread_mutex_t *mutex);
// int pthread_mutex_unlock(pthread_mutex_t *mutex);
/*
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex,
const struct timespec *restrict abstime);
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
*/
//posix 线程库的函数 线程库
void *consume(void* arg)
{
int inum = 0;
inum = (int)arg;
while(1)
{
pthread_mutex_lock(&mutex);
printf("consum:%d\n", inum);
while (g_Count == 0) //while 醒来以后需要重新判断 条件g_Count是否满足,如果不满足,再次wait
{
printf("consum:%d 开始等待\n", inum);
pthread_cond_wait(&my_condition, &mutex); //api做了三件事情 //pthread_cond_wait假醒
printf("consum:%d 醒来\n", inum);
}
printf("consum:%d 消费产品begin\n", inum);
g_Count--; //消费产品
printf("consum:%d 消费产品end\n", inum);
pthread_mutex_unlock(&mutex);
sleep(1);
}
pthread_exit(0);
}
//生产者线程
//
void *produce(void* arg)
{
int inum = 0;
inum = (int)arg;
while(1)
{
/*
//因为是很多生产者调用produce,要保护全局变量g_Count,所以加锁
pthread_mutex_lock(&mutex);
if (g_Count > 20)
{
printf("produce:%d 产品太多,需要控制,休眠\n", inum);
pthread_mutex_unlock(&mutex);
sleep(1);
continue;
}
else
{
pthread_mutex_unlock(&mutex);
}
*/
pthread_mutex_lock(&mutex);
printf("产品数量:%d\n", g_Count);
printf("produce:%d 生产产品begin\n", inum);
g_Count++;
//只要我生产出一个产品,就告诉消费者去消费
printf("produce:%d 生产产品end\n", inum);
printf("produce:%d 发条件signal begin\n", inum);
pthread_cond_signal(&my_condition); //通知,在条件上等待的线程
printf("produce:%d 发条件signal end\n", inum);
pthread_mutex_unlock(&mutex);
sleep(1);
}
pthread_exit(0);
}
//结论:return arg 和 pthread_exit()的结果都可以让pthread_join 接过来
int main()
{
int i =0;
pthread_t tidArray[CUSTOM_COUNT+PRODUCT_COUNT+10];
//创建消费者线程
for (i=0; i<CUSTOM_COUNT; i++)
{
pthread_create(&tidArray[i], NULL, consume, (void *)i);
}
sleep(1);
//创建生产线程
for (i=0; i<PRODUCT_COUNT; i++)
{
pthread_create(&tidArray[i+CUSTOM_COUNT], NULL, produce, (void*)i);
}
for (i=0; i<CUSTOM_COUNT+PRODUCT_COUNT; i++)
{
pthread_join(tidArray[i], NULL); //等待线程结束。。。
}
printf("进程也要结束1233\n");
return 0;
}
条件变量的优点:
相较于mutex而言,条件变量可以减少竞争。
如直接使用mutex,除了生产者、消费者之间要竞争互斥量以外,消费者之间也需要竞争互斥量,但如果汇聚(链表)中没有数据,消费者之间竞争互斥锁是无意义的。有了条件变量机制以后,只有生产者完成生产,才会引起消费者之间的竞争。提高了程序效率。