1.为什么引出条件变量?
举个简单的例子,应用程序A中包含两个线程t1和t2。t1需要在 bool变量test_cond为true时才能继续执行,而test_cond的值是由t2来改变的,这种情况下,如何来写程序呢?可供选择的方案有两 种:
(1)第一种是t1定时的去轮询变量test_cond,如果test_cond为false,则继续休眠;如果test_cond为 true,则开始执行。
(2)第二种就是上面提到的条件变量,t1在test_cond为false时调用cond_wait进行等待,t2在改变test_cond的值后,调用cond_signal,唤醒在等待中的t1,告诉t1 test_cond的值变了,这样t1便可继续往下执行。
[性能对比分析]
很明显,上面两种方案中,第二种方案是比较优的。在第一种方案中,在每次轮询时,如果t1休眠的时间比较短,会导致cpu浪费很厉害;如果t1休眠的时间 比较长,又会导致应用逻辑处理不够及时,致使应用程序性能下降。第二种方案就是为了解决轮询的弊端而生的。
2.条件变量
1.条件变量允许线程以无竞争的方式等待特定的条件发生。(条件本身是由互斥量保护的。线程在改变条件变量状态前必须先锁住互斥量。)
2.条件变量是在多线程程序中用来实现**“等待->唤醒”**逻辑常用的方法(条件变量不是锁!它可以造成线程阻塞,通常与互斥锁配合使用)
3.条件变量+互斥锁(一起使用)
互斥锁:线程同步
条件变量:阻塞线程
4.条件变量主要包括两个动作:一个线程等待“条件变量的成立”,条件不成立时线程挂起;另一个线程修改条件,并判断条件是否成立,当“条件成立”时,唤醒等待条件成立的线程。
条件变量的万能公式
# 消费者代码
while True:
pthread_mutex_lock(mutex)
while(条件不满足){
pthread_cond_wait(cond, mutex);
}
修改条件
pthread_mutex_unlock(mutex)
# 生产者代码
while True:
pthread_mutex_lock(mutex)
修改条件
if(条件满足){
pthread_cond_signal(&cond)
}
pthread_mutex_unlock(&mutex);
3.条件变量API
pthread_cond_t cond; //条件变量类型
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr); //初始化
int pthread_cond_destroy(pthread_cond_t *cond); //销毁
//当条件不到来时,仅仅只阻塞一定的时长
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);
//唤醒[至少一个]阻塞在条件变量上等待的线程
int pthread_cond_signal(pthread_cond_t *cond);
//广播:唤醒[全部]的阻塞在条件变量上等待的线程
int pthread_cond_broadcast(pthread_cond_t *cond);
[注1] pthread_cond_wait,内核会做下面这些事
1.拿到锁的线程,把锁暂时释放;
2.线程休眠,进行等待;
3.线程等待通知,要醒来。(重新获取锁)
[注2] pthread_cond_timedwait函数的参数abstime
struct timespec { //绝对时间
time_t tv_sec; /* seconds */ 秒
long tv_nsec; /* nanosecondes*/ 纳秒
}
相对时间/绝对时间:time(NULL)返回的就是绝对时间。而alarm(1)是相对时间,相对当前时间定时1秒钟
abstime的正确用法
time_t cur = time(NULL); //获取当前时间。
struct timespec t; //定义timespec 结构体变量t
t.tv_sec = cur+1; //定时1秒
pthread_cond_timedwait (&cond, &mutex, &t); //传参
4.示例代码
4.1 [单生产者/单消费者]单链表的插入和删除
/*作为公享数据,需被互斥量保护*/
typedef struct node{//声明链表类型
int data;
struct node* next;
}LNode;
LNode* head=NULL; //临界资源:链表的头节点
pthread_mutex_t mutex;
pthread_cond_t cond;
void* producer(void* arg){
while(1){
LNode* node=(LNode*)malloc(sizeof(LNode)); //创建新的节点node
node->data=rand()%100;
pthread_mutex_lock(&mutex);
node->next=head; //将node采用头插法插入链表
head=node;
printf(" produce node=%d\n",node->data);
pthread_mutex_unlock(&mutex);
sleep(rand()%3);
pthread_cond_signal(&cond); //插入node后,唤醒等待的线程去消费链表
}
return NULL;
}
void* customer(void* arg){
while(1){
pthread_mutex_lock(&mutex);
while(head==NULL){ //if(head==NULL) 如果链表为NULL,则一直阻塞
pthread_cond_wait(&cond,&mutex);
}
//采用头删法删除节点
LNode* delNode=head;
head=head->next; //delete head
printf("delete node=%d\n",delNode->data);
free(delNode);
pthread_mutex_unlock(&mutex);
sleep(rand()%3);
}
return NULL;
}
int main(){
pthread_mutex_init(&mutex,NULL);
pthread_cond_init(&cond,NULL);
pthread_t tid1,tid2;
pthread_create(&tid1,NULL,producer,NULL);
pthread_create(&tid2,NULL,customer,NULL);
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}
4.2 条件变量之(多生产者/多消费者模型)
1.两个条件变量cond_full/cond_empty,分别控制缓冲区满/空
2.mutex控制全局变量g_count(消费者将g_count每次减去1,生产者将g_count每次加1)
为什么要使用互斥锁配合条件变量使用?
因为涉及到多个线程
对全局变量g_count进行操作,所以要用线程互斥锁对g_count进行控制;所以首先定义互斥锁mutex,然后调用pthread_mutex_lock(&mutex)进行上锁,对g_count进行操作之后再调用pthread_mutex_unlock(&mutex)进行解锁;
代码细节:
1.先创建消费者线程后sleep(3),此时缓冲区中无产品,因此消费者会一直阻塞
2.消费者线程数=2,生产者线程数=3
3.生产者生产完产品后sleep(1),消费者消费完产品后sleep(3)
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>
#define MAXSIZE 10
#define CUSTOM_COUNT 2
#define PRODUCT_COUNT 3
int produce_id = 0;
int custome_id = 0;
int g_count = 0; //全局变量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 一个互斥锁
pthread_cond_t cond_full = PTHREAD_COND_INITIALIZER; // 两个条件变量
pthread_cond_t cond_empty=PTHREAD_COND_INITIALIZER;
void *consume(void *arg)
{
int id = ++custome_id;
while(1)
{
pthread_mutex_lock(&mutex);
while(g_count == 0) //醒来以后需要重新判断条件是否满足,如果不满足,再次等待
{
printf(" buffer is empty, [%d consumer] waiting...\n", id);
pthread_cond_wait(&cond_empty, &mutex);
}
printf(" [%d consumer] consume [g_count=%d] \n", id, g_count);
g_count--;
pthread_cond_signal(&cond_full);
pthread_mutex_unlock(&mutex);
sleep(3);
}
}
void *produce(void *arg)
{
int id = ++produce_id;
while(1)
{
pthread_mutex_lock(&mutex);
while(g_count >= MAXSIZE)
{
printf("buffer is full, [%d producer] waiting...\n", id);
pthread_cond_wait(&cond_full,&mutex);
}
g_count++;
printf("[%d producer] produce [g_count=%d] \n", id, g_count);
pthread_cond_signal(&cond_empty);
pthread_mutex_unlock(&mutex);
sleep(1);
}
}
void handler(int signo)
{
if(signo==SIGINT)
{
pthread_cond_destroy(&cond_full);
pthread_cond_destroy(&cond_empty);
exit(0);
}
}
int main()
{
signal(SIGINT,handler);
int i = 0;
pthread_t tidCustom[CUSTOM_COUNT];
pthread_t tidProduce[PRODUCT_COUNT];
for (i = 0; i < CUSTOM_COUNT; ++i) /*创建消费者线程*/
pthread_create(&tidCustom[i], NULL, consume, NULL);
sleep(3);
for (i = 0; i < PRODUCT_COUNT; ++i) /*创建生产者线程*/
pthread_create(&tidProduce[i], NULL, produce, NULL);
for (i = 0; i < CUSTOM_COUNT; ++i) /*等待消费者线程*/
pthread_join(tidCustom[i], NULL);
for (i = 0; i < PRODUCT_COUNT; ++i) /*等待生产者线程*/
pthread_join(tidProduce[i], NULL);
return 0;
}