线程同步 条件变量
补充文档
条件变量
锁的不足:只能给一个线程放行,剩下的还需要阻塞
条件变量:可以让多个线程访问临界区,但会出现混乱问题,仍需要和互斥锁搭配使用
为什么
使用场景复杂,适用于生产者消费者模型
多个生产者对应多个线程
多个消费者也对应多个线程
生产者生产满了之后,不能生产,使用条件变量进行阻塞,消费者进行消费
消费之后,条件变量通知生产者进行生产,
当消费完了,仓库为空,条件变量通知消费者停止消费
两者之间是一个相互协作的关系。
函数
变量定义
pthread_cond_t cond;
存放的信息:
- 被条件变量阻塞的线程信息
初始化和销毁
#include<pthread_h>
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 *attr);
cond:条件变量的地址
attr:属性
线程阻塞
//线程阻塞函数,哪个线程调用这个函数,哪个线程就会被阻塞
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
- 在阻塞线程时候,如果线程已经对互斥锁mutex上锁,那么会将这把锁打开,这样做是为了避免死锁
- 当线程解除阻塞的时候,函数内部会帮助这个线程再次将这个mutex互斥锁锁上,继续向下访问临界区
-
阻塞等待条件变量cond
-
释放已掌握的互斥锁(解锁互斥量)相当于ptherad_mutex_unlock(&mutex);
1、2两步为一个原子操作
- 当被唤醒,pthread_cond_wait函数返回时,解除阻塞并重新申请获取互斥锁pthread_mutex_lock(&mutex);
固定时间阻塞
// 表示的时间是从1971.1.1到某个时间点的时间, 总长度使用秒/纳秒表示
struct timespec {
time_t tv_sec; /* Seconds */
long tv_nsec; /* Nanoseconds [0 .. 999999999] */
};
// 将线程阻塞一定的时间长度, 时间到达之后, 线程就解除阻塞了
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
这个函数的前两个参数和pthread_cond_wait函数是一样的,第三个参数表示线程阻塞的时长,
但是需要额外注意一点:struct timespec这个结构体中记录的时间是从1971.1.1到某个时间点的时间,
总长度使用秒/纳秒表示。因此赋值方式相对要麻烦一点:
time_t mytim = time(NULL); // 1970.1.1 0:0:0 到当前的总秒数
struct timespec tmsp;
tmsp.tv_nsec = 0;
tmsp.tv_sec = time(NULL) + 100; // 线程阻塞100s
// 唤醒阻塞在条件变量上的线程, 至少有一个被解除阻塞
int pthread_cond_signal(pthread_cond_t *cond);
// 唤醒阻塞在条件变量上的线程, 被阻塞的线程全部解除阻塞
int pthread_cond_broadcast(pthread_cond_t *cond);
生产者和消费者
场景描述:使用条件变量实现生产者和消费者模型,生产者有5个,往链表头部添加节点,消费者也有5个,删除链表头部的节点。
给出示例代码
#include<pthread.h>
#include<string.h>
#include<iostream>
#include<stdlib.h>
#include<bits/stdc++.h>
using namespace std;
pthread_cond_t cond1;
pthread_mutex_t mutex1;
//链表定义
struct Node
{
int number;
Node* next;
};
Node* head=NULL;
//生产者
void* produce(void* arg)
{
while(1)
{
//加一个互斥锁
pthread_mutex_lock(&mutex1);
//创建一个新的节点,临界区,需要锁
Node* newnode=new Node();
newnode->number=rand()%1000;
newnode->next=head;
head=newnode;
cout<<"生产者:"<<pthread_self()<<" number: "<<newnode->number<<endl;
pthread_mutex_unlock(&mutex1);
//在此唤醒消费者线程
pthread_cond_broadcast(&cond1);
sleep(rand()%3);
}
return NULL;
}
//消费者
void* consume(void* arg)
{
while(1)
{
pthread_mutex_lock(&mutex1);
while(head==NULL)
{
//需要阻塞消费者线程
pthread_cond_wait(&cond1,&mutex1);
//需要生产者线程唤醒该线程
}
Node* node=head;
cout<<"消费者:"<<pthread_self()<<" number: "<<node->number<<endl;
head=head->next;
delete node;
pthread_mutex_unlock(&mutex1);
sleep(rand()%3);
}
return NULL;
}
int main()
{
//条件变量和互斥锁的创建
pthread_mutex_init(&mutex1,NULL);
pthread_cond_init(&cond1,NULL);
//线程的创建
pthread_t p[5],c[5];
for(int i=0;i<5;i++)
{
pthread_create(&p[i],NULL,produce,NULL);
}
for(int i=0;i<5;i++)
{
pthread_create(&c[i],NULL,consume,NULL);
}
//锁的释放
for(int i=0;i<5;i++)
{
pthread_join(p[i],NULL);
pthread_join(c[i],NULL);
}
//锁的释放
pthread_mutex_destroy(&mutex1);
pthread_cond_destroy(&cond1);
}
代码分析:
假设消费者抢到了cpu的时间片
- 由于链表为空,消费者线程被阻塞到了
pthread_cond_wait
上 - 其他的消费者线程被阻塞到了mutex_lock上
- 但是被cond_wait阻塞的时候,会被cond_wait给打开,解除阻塞的时候,会将互斥锁重新锁上
- 继续向下执行
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理