目录
一 条件变量
前面的文章已经讲过互斥锁的使用规则,接下来,我们再来讲一下条件变量。
条件变量其实就是一种通知机制,当某个共享数据到达某个值的时候,通知等待该共享数据到达该值的线程来处理该共享数据。
条件变量一般是用于让生产者线程和消费者线程之间以及消费者线程之间互斥的访问临界资源(临界资源指的是同时只允许一个线程访问的共享资源)。条件变量的使用一般是需要结合互斥锁来进行(条件变量相关函数的输入参数就是互斥锁),从而使得各个线程能够互斥的访问该临界资源。
二 条件变量的使用
条件变量在pthread.h头文件中。
2.1 条件变量的初始化
int pthread_cond_init(pthread_cond_t*cond,const pthread_condattr_t*cond_attr);
函数功能:用于初始化一个条件变量。
函数参数:
1.cond:用于指向目标条件变量。
2.cond_attr:用于设置条件变量属性,置NULL表示条件变量属性为默认值。
2.2 条件变量的销毁
int pthread_cond_destroy(pthread_cond_t*cond);
函数功能:用于销毁条件变量。
2.3 条件变量的通知机制
int pthread_cond_signal(pthread_cond_t*cond);
int pthread_cond_broadcast(pthread_cond_t*cond);
函数功能:
条件变量的通知方法有两种:
1.根据线程的优先级和调度策略唤醒一个等待条件变量的线程的pthread_cond_signal()函数
2.唤醒所有等待条件变量的线程的pthread_cond_broadcast()函数。
2.4 条件变量的等待
int pthread_cond_wait(pthread_cond_t*cond,pthread_mutex_t* mutex);
函数功能:
用于阻塞等待某个条件变量。函数的内部实现机制是:
1.当调用该函数前,一般会判断某个临界资源是否满足要求,如果不满足,则调用该函数,函数内部实现:将该线程挂起到等待条件变量队列,然后解除互斥锁;
2.当该线程被唤醒时,会再次对互斥锁加锁,然后返回,从而继续判断临界资源是否满足条件。
2.5 条件变量为什么要和互斥锁结合使用
我个人的理解是,条件变量主要是使用一种通知机制来让各个线程来处理临界资源,但是为了保证线程安全,需要使得各个线程来互斥的访问临界资源:
如果没有互斥锁,则:
1.调用条件变量通知函数唤醒所有的等待线程的话,很有可能导致临界资源被多个线程重复处理,甚至导致错误的发生,因此为了方便,每个线程被唤醒返回pthread_cond_wait()函数时,一般会对互斥锁加锁,这样其他被唤醒的线程只能阻塞等待此线程释放该互斥锁,从而实现互斥的对于临界资源的访问。
2.当此线程处理完临界资源后会在此调用pthread_cond_wait()函数,将该线程挂起至等待条件变量队列,然后释放互斥锁,让其他线程被唤醒继续处理该临界资源。
三 测试案例
测试代码:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
pthread_mutex_t mutex;
pthread_cond_t cond;
struct ListNode{
int data;
struct ListNode* next;
};
struct ListNode* head;
struct ListNode* addnode()
{
int data = rand() % 10;
printf("%d\n", data);
struct ListNode* node = (struct ListNode*)malloc(sizeof(struct ListNode));
node->data = data;
node->next = head->next;
head->next = node;
}
int getnode()
{
struct ListNode* node = head->next;
head->next = head->next->next;
int data = node->data;
free(node);
return data;
}
void* pthread()
{
sleep(2);
while(1)
{
pthread_mutex_lock(&mutex);
printf("------生产数据------\n");
for(int i = 0; i < 3; i++)
addnode();
pthread_mutex_unlock(&mutex);
//pthread_cond_signal(&cond);
pthread_cond_broadcast(&cond);
sleep(1);//如果不睡眠可能一直都轮不到消费者取数据:生产者继续加锁生产数据
}
}
void* cthread()
{
while(1)
{
pthread_mutex_lock(&mutex);
while(head->next == NULL)
pthread_cond_wait(&cond, &mutex);
printf("------消费数据------\n");
int data = getnode();
printf("%d\n", data);
sleep(1);
pthread_mutex_unlock(&mutex);
}
}
int main()
{
head = (struct ListNode*)malloc(sizeof(struct ListNode));
head->next = NULL;
time_t t;
srand((unsigned) time(&t));
int ret = pthread_cond_init(&cond, NULL);
if(ret != 0)
{
printf("创建条件变量失败");
return -1;
}
ret = pthread_mutex_init(&mutex, NULL);
if(ret != 0)
{
printf("创建互斥锁失败");
return -1;
}
pthread_t pid;
ret = pthread_create(&pid, NULL, pthread, NULL);
if(ret != 0)
{
printf("创建生产线程失败");
return -1;
}
pthread_t cid1;
ret = pthread_create(&cid1, NULL, cthread, NULL);
if(ret != 0)
{
printf("创建消费线程1失败");
return -1;
}
pthread_t cid2;
ret = pthread_create(&cid2, NULL, cthread, NULL);
if(ret != 0)
{
printf("创建消费线程2失败");
return -1;
}
pthread_join(pid, NULL);
pthread_join(cid1, NULL);
pthread_join(cid2, NULL);
pthread_cond_destroy(&cond);
pthread_mutex_destroy(&mutex);
return 0;
}
用pthread_cond_signal()函数消费者线程去取数据:
用pthread_cond_broadcast()函数通知消费者线程去取数据:
原因分析:
1.如果用pthread_cond_signal()函数,只会唤醒一个消费者线程,则当消费者线程取完数据,释放互斥锁后会立即执行等待互斥锁释放的生产者线程,就会导致数据还未完全取出。
2.如果用pthread_cond_broadcast()函数,会唤醒所有等待条件变量的消费者线程,则当消费者线程取完数据,释放互斥锁后会继续执行其他调用pthread_cond_wait()函数等待加锁返回的消费者线程继续获取数据。
注意事项:
工作线程通知完消费者线程后建议立即调用sleep()函数休眠,防止继续执行工作线程代码存入数据,消费者线程无法执行获取数据。