条件变量
条件变量,通知状态的改变。条件变量允许一个线程就某个共享变量的状态变化通知其他线程,并让其他线程等待(阻塞于)这一通知。
条件变量总是结合互斥量使用。条件变量就共享变量的状态改变发出通知,而互斥量则提供对该共享变量访问的互斥。
演示程序:
定义一个变量a,线程1当a为0时对a+1,线程2当a为1时对a-1,循环10次,a的结果应该是0和1交替。
两个线程需要不断的轮询结果,造成CPU浪费。可以使用条件变量解决:线程1不满足运行条件时,先休眠等待,其他线程运行到满足线程1的运行条件时,通知并唤醒线程⒉继续执行。
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<errno.h>
#include<stdlib.h>
#include<time.h>
pthread_rwlock_t mtx;//定义读写锁
int a = 0;
void* thread1(void* arg) {
for(int i = 0; i < 10; i++) {
while(1) {
pthread_mutex_lock(&mtx);
if(a == 0) {
a++;
printf("a = %d\n", a);
pthread_mutex_unlock(&mtx);
break;
}
pthread_mutex_unlock(&mtx);
}
}
}
void* thread2(void* arg) {
for(int i = 0; i < 10; i++) {
while(1) {
pthread_mutex_lock(&mtx);
if(a == 1) {
a--;
printf("a = %d\n", a);
pthread_mutex_unlock(&mtx);
break;
}
pthread_mutex_unlock(&mtx);
}
}
}
int main(int argc, char* argv[])
{
pthread_mutex_init(&mtx, NULL);
pthread_t tid1;
pthread_t tid2;
pthread_create(&tid1, NULL, thread1, NULL);
pthread_create(&tid2, NULL, thread2, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
return 0;
}
但是会造成CPU浪费,通过条件变量实现通知
条件变量函数
●int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
参数attr表示条件变量的属性,默认值传NULL
●int pthread_cond_destroy(pthread_cond_t *cond);
●int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
阻塞等待一个条件变量,释放持有的互斥锁,这两步是原子操作
被唤醒时,函数返回,解除阻塞并重新申请获取互斥锁
●int pthread_cond_signal(pthread_cond_t *cond);
唤醒一个阻塞在条件变量上的线程
●int pthread_cond_broadcast(pthread_cond_t *cond);
唤醒全部阻塞在条件变量上的线程
● int pthread_cond_timedwait(pthread_cond_t *cond,pthread_mutex_t *mutex,
const struct timespec*abstime);
限时等待一个条件变量
参数abstime是一个timespec结构体,以秒和纳秒表示的绝对时间
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<errno.h>
#include<stdlib.h>
#include<time.h>
pthread_rwlock_t mtx;
pthread_cond_t cond;
int a = 0;
void* thread1(void* arg) {
for(int i = 0; i < 10; i++) {
pthread_mutex_lock(&mtx);
if(a != 0) {
pthread_cond_wait(&cond, &mtx);//阻塞等待,释放锁,被唤醒时重新抢锁
}
a++;
printf("a = %d\n", a);
pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&mtx);
}
}
void* thread2(void* arg) {
for(int i = 0; i < 10; i++) {
pthread_mutex_lock(&mtx);
if(a != 1) {
pthread_cond_wait(&cond, &mtx);
}
a--;
printf("a = %d\n", a);
pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&mtx);
}
}
int main(int argc, char* argv[])
{
pthread_mutex_init(&mtx, NULL);
pthread_cond_init(&cond, NULL);
pthread_t tid1;
pthread_t tid2;
pthread_create(&tid1, NULL, thread1, NULL);
pthread_create(&tid2, NULL, thread2, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
return 0;
}
从主动通知代替了轮询
虚假唤醒:不该唤醒的时候唤醒了。
pthread_cond_wait外面如果使用if 判断,再增加两个线程,会出现虚假唤醒
1.假设线程1抢到了锁,加1,通知所有线程抢锁
2.线程⒉抢到了锁,等待并释放锁
3.线程3抢到了锁,减1,通知所有线程抢锁
4.线程1抢到了锁,等待并释放锁
5.注意这里,如果线程3抢到了锁,wait结束,线程继续执行,+1,number变成2
6.number出现问题,不是0和1了,原因是wait结束时没有再次判断number的值
pthread_cond_wait外面使用循环判断,防止出现虚假唤醒
所有的if都换成while就解决了
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<errno.h>
#include<stdlib.h>
#include<time.h>
//同样操作的线程只有一个才没事
pthread_rwlock_t mtx;
pthread_cond_t cond;
int a = 0;
void* thread1(void* arg) {
for(int i = 0; i < 10; i++) {
pthread_mutex_lock(&mtx);
while(a != 0) {
pthread_cond_wait(&cond, &mtx);//阻塞等待,释放锁,被唤醒时重新抢锁
}
a++;
printf("a = %d\n", a);
pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&mtx);
}
}
void* thread2(void* arg) {
for(int i = 0; i < 10; i++) {
pthread_mutex_lock(&mtx);//一个线程加锁,所有线程都阻塞在lock上
while(a != 1) {
pthread_cond_wait(&cond, &mtx);
}
a--;
printf("a = %d\n", a);
pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&mtx);
}
}
void* thread3(void* arg) {
for(int i = 0; i < 10; i++) {
pthread_mutex_lock(&mtx);
while(a != 0) {
pthread_cond_wait(&cond, &mtx);//阻塞等待,释放锁,被唤醒时重新抢锁
}
a++;
printf("a = %d\n", a);
pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&mtx);
}
}
void* thread4(void* arg) {
for(int i = 0; i < 10; i++) {
pthread_mutex_lock(&mtx);
while(a != 1) {
pthread_cond_wait(&cond, &mtx);
}
a--;
printf("a = %d\n", a);
pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&mtx);
}
}
int main(int argc, char* argv[])
{
pthread_mutex_init(&mtx, NULL);
pthread_cond_init(&cond, NULL);
pthread_t tid1;
pthread_t tid2;
pthread_t tid3;
pthread_t tid4;
pthread_create(&tid1, NULL, thread1, NULL);
pthread_create(&tid2, NULL, thread2, NULL);
pthread_create(&tid3, NULL, thread3, NULL);
pthread_create(&tid4, NULL, thread4, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_join(tid3, NULL);
pthread_join(tid4, NULL);
return 0;
}
生产者消费者模型
线程同步典型的案例即为生产者消费者模型,而借助条件变量来实现这一模型,是比较常见的一种方法。假定有两个线程,一个模拟生产者行为,一个模拟消费者行为。两个线程同时操作一个共享资源(一般称之为汇聚),生产者向其中添加产品,消费者从中消费掉产品。
相较于互斥量而言,条件变量可以减少竞争。如直接使用互斥量,除了生产者、消费者之间要竞争互斥量以外,消费者之间也需要竞争互斥量,但如果汇聚(链表)中没有数据,消费者之间竞争互斥量是无意义的。有了条件变量机制以后,只有生产者完成生产,才会引起消费者之间的竞争。提高了程序效率。
场景:一个线程产生随机数放入链表中,一个线程从链表中取出一个随机数打印
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<errno.h>
#include<stdlib.h>
#include<time.h>
pthread_mutex_t mtx;
pthread_cond_t cond;
int a = 0;
struct Node {
int number;
struct Node* next;
};
struct Node* head;
struct Node* append_node(int number) {//添加节点
struct Node* new_node = (struct Node*)malloc(sizeof(struct Node));
new_node->number = number;
new_node->next = head;
head = new_node;
return head;
}
int pop_head_node() {//移除头结点
if (head == NULL) {
return -1;
}
struct Node* temp = head;
int number = temp->number;
head = head->next;
free(temp);
return number;
}
void* producer(void* arg) {
while(1) {
pthread_mutex_lock(&mtx);
append_node(rand() % 500 + 1);//产生随机数放到链表里
pthread_cond_broadcast(&cond);//加一个唤醒
pthread_mutex_unlock(&mtx);
sleep(1);
}
}
void* customer(void* arg) {
while(1) {
pthread_mutex_lock(&mtx);
while(head == NULL) {
pthread_cond_wait(&cond, &mtx);
}
int number = pop_head_node();//把移除节点的数字拿出来
printf("number = %d\n", number);
pthread_mutex_unlock(&mtx);
}
}
int main(int argc, char* argv[])
{
pthread_mutex_init(&mtx, NULL);
pthread_cond_init(&cond, NULL);
srand(time(NULL));
pthread_t tid1;
pthread_t tid2;
pthread_create(&tid1, NULL, producer, NULL);
pthread_create(&tid2, NULL, customer, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
return 0;
}