1 条件变量
使用读写锁、互斥锁可以阻塞线程,使对临界代码的操作变为原子操作。其临界区代码只有两种状态,锁定和非锁定。而如果需要外部的条件判断之后才执行某些临界区代码,比如经典的生产者-消费者模型,生产者和消费者对资源的使用时互斥的,但是消费者对资源的使用还有一个前提就是资源不为空,但是这个判断互斥锁没法实现。
因此需要使用条件变量,在消费者进入临界区并被告知当前无可用资源的时候,阻塞消费者线程;当生产者生产了可供消费者使用的资源之后,提示消费者可以使用资源(唤醒阻塞的消费者线程)。比如下面的代码:
链表Node *head=NULL
while(head){
//想让代码在这个位置阻塞
//等待链表中有了节点之后再继续向下运行
//使用了后面要用的条件变量 – 阻塞线程
}
//链表不为空的处理代码
1.1 条件变量是锁吗?
条件变量不是锁,但是条件变量可以阻塞线程。
使用条件变量和互斥锁,其中:
互斥锁:保护一段共享区域;
条件变量:引起阻塞。
下面是经典的生产者-消费者模型。
生产者和消费者模型:生产者生产商品,消费者消费商品,共享“商品”资源。
消费者:一直消费
//一直消费
while(1){
//判断是不是有烧饼
pthread_mutex_lock(&mutex);
if(head==NULL){
//阻塞等待
pthread_cond_wait(&cond,&mutex);
}
//吃烧饼
pthread_mutex_lock(&mutex);
sleep(1);
}
生产者:
//一直生产
while(1){
//创建一个节点
pthread_mutex_lock(&mutex);
//节点插入到链表
pthread_mutex_unlock(&mutex);
//通知阻塞的消费者解除阻塞
pthread_cond_signal();
}
1.2 条件变量的两个动作
- 条件不满足时,阻塞线程;
- 条件满足时,通知阻塞的线程开始工作。
1.3 条件变量的类型
//类型名 变量名
pthread_cond_t cond
1.4 主要函数
(1) 初始化一个条件变量
pthread_cond_init(
pthread_cond_t* restrict cond,//条件变量类型的变量
const pthread_condattr_t* restrict attr//参数,默认NULL
);
(2) 销毁一个条件变量
pthread_cond_destroy(pthread_cond_t* cond);
(3) 阻塞等待一个条件变量
pthread_cond_wait(
pthread_cond_t* restrict cond, //条件变量类型的变量
pthread_mutex_t* restrict mutex//互斥锁类型的变量
);
使用条件变量阻塞线程:传入参数为条件变量类型的变量以及互斥锁类型的变量;
使用条件变量阻塞线程时:将已经上锁的mutex解锁;
条件变量通知解除阻塞之后:加锁,访问共享数据。
(4) 限时等待一个条件变量
pthread_cond_timedwait(
pthread_cond_t* restrict cond,
pthread_mutex_t* restrict mutex,
const struct timespec* restrict abstime
);
(5)唤醒至少一个阻塞在条件变量上的线程
pthread_cond_signal(pthread_cond_t* cond);
(6)唤醒全部阻塞在条件变量上的线程
pthread_cond_broadcast(pthread_cond_t* cond);
2. 代码示例(C语言)
//消费者,生产者模型
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <pthread.h>
#include <windows.h>
//创建节点的结构
typedef struct node{
int data;
struct node* next;
}Node;
//定义一个指针,指向链表头部
Node* head=NULL;
//线程同步需要互斥锁
pthread_mutex_t mutex;
//条件变量
pthread_cond_t cond;
//生产者
void* producer(void* arg){
while(1){
//创建一个链表的节点
Node* pnew=(Node*)malloc(sizeof(Node));
//节点初始化
pnew->data=rand()%1000;
//使用互斥锁
pthread_mutex_lock(&mutex);
//指针域
pnew->next=head;
head=pnew;//节点被插入到链表头部
printf("===== produce: %lu, %d\n",pthread_self(),pnew->data);
//解锁
pthread_mutex_unlock(&mutex);
//通知阻塞的消费者线程 ,解除阻塞
pthread_cond_signal(&cond);
sleep(rand()%3);
}
return NULL;
}
void* customer(void* arg){
while(1){
//加锁
pthread_mutex_lock(&mutex);
if(head==NULL){
//线程阻塞
//该函数会对互斥锁解锁
pthread_cond_wait(&cond,&mutex);
//解除阻塞之后,执行加锁操作
}
//链表不为空,删除头节点
Node* pdel=head;
head=head->next;
printf("===== customer: %lu, %d\n",pthread_self(),pdel->data);
free(pdel);
//解锁
pthread_mutex_unlock(&mutex);
}
return NULL;
}
int main(void){
pthread_t p1, p2;
pthread_mutex_init(&mutex,NULL);
pthread_cond_init(&cond,NULL);
//创建生产者线程
pthread_create(&p1,NULL,producer,NULL);
pthread_create(&p1,NULL,customer,NULL);
//阻塞回收
pthread_join(p1,NULL);
pthread_join(p2,NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}