【线程】线程的同步---生产消费者模型

本文重点:理解条件变量和生产者消费者模型

同步是在保证数据安全的情况下,让我们的线程访问资源具有一定的顺序性

条件变量cond

当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了,就是申请锁失败了,它就要添加到一个队列中等待,这个队列叫做等待队列,把这个等待队列可以叫做条件变量,也就是说申请锁失败,进行等待是在条件变量下等的,这样它们排好队,就有顺序性。

条件变量依赖于锁的实现

那我怎么知道几时要在条件变量下等呢?

一定是临界资源不就绪,没错,临界资源也是有状态的!!

你怎么知道临界资源是就绪还是不就绪的?

你判断出来的!判断是访问临界资源吗?必须是的,也就是判断必须在加锁之后!!

条件变量接口

初始化接口和锁是一样的

pthread_cond_t: 系统提供的类型

cond:要初始化的条件变量
attr:条件变量的属性,设为nullptr

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
全局的条件变量,自动初始化和销毁

等待条件满足

cond:要在这个条件变量上等待
mutex:锁,后面详细解释 

 唤醒等待

signal是唤醒一个线程,broadcast是唤醒条件变量下的全部线程 

下面通过代码来理解上面的问题

注意函数接口的位置,想想为什么函数是在这?

#include<iostream>
#include<unistd.h>
#include<pthread.h>using namespace std;int cnt=0;
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;void* Count(void* args)
{pthread_detach(pthread_self());//分离线程uint64_t number=(uint64_t)args;cout<<"pthread: "<<number<<",create success"<<endl;while(1){pthread_mutex_lock(&mutex);// 我们怎么知道我们要让一个线程去休眠了那?一定是临界资源不就绪,没错,临界资源也是有状态的!!// 你怎么知道临界资源是就绪还是不就绪的?你判断出来的!判断是访问临界资源吗?必须是的,也就是判断必须在加锁之后!!!pthread_cond_wait(&cond,&mutex);//为什么这个函数在这里?因为临界资源不就绪,就要等待//为什么还要一个锁的参数?因为这个线程在前面的时候申请了锁,此时线程持有锁//1.pthread_cond_wait让线程等待的时候,会自动释放锁!2.唤醒而返回的时候,重新持有锁cout<<"pthread: "<<number<<", cnt: "<<cnt++<<endl;pthread_mutex_unlock(&mutex);sleep(1);}return nullptr;
}
int main()
{for(uint64_t i=0;i<5;i++){pthread_t tid;pthread_create(&tid,nullptr,Count,(void*)i);usleep(1000);}//让主线程把等待的线程唤醒while(1){sleep(1);pthread_cond_signal(&cond);//唤醒条件变量下的一个线程,默认第一个开始cout<<"signal one thread..."<<endl;}return 0;
}

 

从结果看出,线程访问临界资源时具有一定的顺序性 

要点: 

pthread_cond_wait函数的位置

我们怎么知道我们要让一个线程去休眠了那?

一定是临界资源不就绪,没错,临界资源也是有状态的!!

你怎么知道临界资源是就绪还是不就绪的?你判断出来的!判断是访问临界资源吗?必须是的,也就是判断必须在加锁之后!!!

为什么这个函数在这里?因为临界资源不就绪,就要等待

为什么还要一个锁的参数?因为这个线程在前面的时候申请了锁,此时线程持有锁

1.pthread_cond_wait让线程等待的时候,会自动释放锁!2.唤醒而返回的时候,重新持有锁

总的来说:条件变量依赖于锁的实现

可能大家还没有很理解,那就在看看下面的生产者消费者模型,再次理解线程怎么等待和唤醒的

生产者消费者模型的原理

producter  consumer

生产者生产产品供货给超市,消费者通过从超市消费,得到产品,超市就相当于一个大的缓存,生产者生产的产品都可以放在超市里,供消费者使用,这样可以提高效率,支持忙闲不均,生产和消费的行为存在一定的解耦(解耦的意思也就是生产者生产是一个执行流,消费者消费是另一个执行流,两个执行流可以同时访问超市资源,就有一定的解耦,不像只有一个执行流时,就不能同时执行)

在生产者生产产品的过程中,消费者是不能访问产品的,但是消费者可以处理产品,相反,在消费者访问产品时,生产者无法生产,但是生产者可以获取数据,这样结合,效率就会很高

记住生产者消费者模型就记住“321”原则

3种关系

2种角色--生产者,消费者

1个场所--超市

生产者消费者模型的实现

思路:创建两个线程,单生产单消费的,一个充当消费者,一个充当生产者,消费者消费任务,生产者生产任务。写一个等待队列的类,方法写生产任务和消费任务,这个是共享资源,需要加锁和解锁,看资源状态来添加条件变量,在写一个任务的类,写清楚是什么任务

大家可以把下面的三个文件的代码拷贝到自己的Visual Studio Code下看,更好看一些,代码是不难得,认真看是能看懂的

一定要特别关注条件变量的接口的使用和他的位置

main.cc

#include "blockqueue.hpp"
#include "task.hpp"
#include<time.h>using namespace std;void *Producter(void *p_args)
{int len=opers.size();BlockQueue<Task>* bq=static_cast<BlockQueue<Task>*>(p_args);while (1){// 模拟生产者获取数据的过程int data1=rand()%10+1;//[1,10]usleep(100);int data2=rand()%10;char op=opers[rand()%len];Task t(data1,data2,op);bq->push(t);//生产任务// cout << "生产了一个任务:" << t.GetTask() <<endl;printf("生产了一个任务: %s\n",t.GetTask().c_str());sleep(1);}return nullptr;
}void *Consumer(void *c_args)
{BlockQueue<Task>* bq=static_cast<BlockQueue<Task>*>(c_args);while (1){//消费任务Task t= bq->pop();//计算t.run();//模拟消费者消费消费数据的过程// cout << "处理任务: " <<t.GetTask()<<" 运算结果:"<<t.GetResult()<<endl;printf("处理任务:%s,运算结果:%s\n",t.GetTask().c_str(),t.GetResult().c_str());}return nullptr;
}int main()
{srand(time(nullptr));pthread_t p, c;BlockQueue<Task>* bq=new BlockQueue<Task>;pthread_create(&p, nullptr, Producter, (void *)bq);pthread_create(&c, nullptr, Consumer, (void *)bq);pthread_join(p, nullptr);pthread_join(c, nullptr);delete bq;return 0;
}

blockqueue.hpp

#pragma once
#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<queue>using namespace std;pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
//生产者和消费者的条件变量要分开的,它们是不同的
pthread_cond_t c_cond=PTHREAD_COND_INITIALIZER;//消费者的条件变量
pthread_cond_t p_cond=PTHREAD_COND_INITIALIZER;//生产者的条件变量template<class T>
class BlockQueue
{
public:BlockQueue(int maxcap=4):_maxcap(maxcap){}T pop(){//加锁pthread_mutex_lock(&mutex);// 我们怎么知道我们要让一个线程去休眠了那?一定是临界资源不就绪,没错,临界资源也是有状态的!!// 你怎么知道临界资源是就绪还是不就绪的?你判断出来的!判断是访问临界资源吗?必须是的,也就是判断必须在加锁之后!!!if(_wait_q.size()==0)//队列为空,临界资源不就绪,就不能消费了,在消费者条件变量下等待{pthread_cond_wait(&c_cond,&mutex);//1.pthread_cond_wait让线程等待的时候,会自动释放锁!2.唤醒而返回的时候,重新持有锁}T out=_wait_q.front();_wait_q.pop();pthread_cond_signal(&p_cond);//消费了一个,生产者肯定可以生产了,唤醒生产者的条件变量下的一个线程pthread_mutex_unlock(&mutex);return out;}void push(const T& in){pthread_mutex_lock(&mutex);if(_wait_q.size()==_maxcap)//队列满了,临界资源不就绪,就不能生产了,在生产者条件变量下等待{pthread_cond_wait(&p_cond,&mutex);}_wait_q.push(in);pthread_cond_signal(&c_cond);//生产了一个,消费者肯定可以消费了,唤醒消费者的条件变量下的一个线程pthread_mutex_unlock(&mutex);}~BlockQueue(){}private:queue<T> _wait_q;//等待队列int _maxcap;//队列的最大容量};

task.hpp

#pragma once
#include <iostream>
#include <string>using namespace std;
string opers="+-*/%";enum
{Divzero = 1,Modzero,Unknown
};class Task
{
public:Task(int data1, int data2, char op) : _data1(data1), _data2(data2), _op(op), _result(0), _exitcode(0){}void run(){switch (_op){case '+':{_result = _data1+_data2;break;}case '-':{_result = _data1-_data2;break;}case '*':{_result = _data1*_data2;break;}case '/':{if (_data2 == 0) _exitcode = Divzero;else _result = _data1/_data2;break;}case '%':{if (_data2 == 0) _exitcode = Modzero;else _result = _data1%_data2;break;}default:{_exitcode=Unknown;break;}}}void operator()(){run();}string GetResult(){string r=to_string(_data1);r+=_op;r+=to_string(_data2);r+='=';r+=to_string(_result);r+='[';r+=to_string(_exitcode);r+=']';return r;}string GetTask(){string r=to_string(_data1);r+=_op;r+=to_string(_data2);r+="=?";return r;}private:int _data1;int _data2;char _op;int _result;int _exitcode;
};

main.cc中打印用printf比较好,用cout我的平台下会有点乱序

 

看懂上面的实现再来看这个问题

什么是伪唤醒?

假如多个生产者生产了四个产品,生产者条件变量下的等待的线程有三个,消费者消费了一个产品,假如用的pthread_cond_broadcast接口,那就唤醒了那三个线程,它们就pthread_cond_wait函数返回,重新持有锁,就生产产品,但是到第二个线程的时候,队列已经满了,第二个线程还会生产 ,那就越界出错了

怎么防止伪唤醒?

就在条件判断时改为循环,不要if,这样函数返回时还要判断(上面的代码还是if,你们自己改吧) 

下面的代码是把上面的实现改为多生产者多消费者

只改了main.cc

#include "blockqueue.hpp"
#include "task.hpp"
#include<time.h>using namespace std;void *Producter(void *p_args)
{int len=opers.size();BlockQueue<Task>* bq=static_cast<BlockQueue<Task>*>(p_args);while (1){// 模拟生产者获取数据的过程int data1=rand()%10+1;//[1,10]usleep(100);int data2=rand()%10;char op=opers[rand()%len];Task t(data1,data2,op);bq->push(t);//生产任务// cout << "生产了一个任务:" << t.GetTask() <<endl;printf("生产了一个任务:%s,thread id: %x\n",t.GetTask().c_str(),pthread_self());sleep(1);}return nullptr;
}void *Consumer(void *c_args)
{BlockQueue<Task>* bq=static_cast<BlockQueue<Task>*>(c_args);while (1){//消费任务Task t= bq->pop();//计算t.run();//模拟消费者消费消费数据的过程// cout << "处理任务: " <<t.GetTask()<<" 运算结果:"<<t.GetResult()<<endl;printf("处理任务:%s,运算结果:%s,thread id: %x\n",t.GetTask().c_str(),t.GetResult().c_str(),pthread_self());}return nullptr;
}int main()
{srand(time(nullptr));pthread_t p[5], c[3];BlockQueue<Task>* bq=new BlockQueue<Task>;for(int i=0;i<5;i++)pthread_create(p+i, nullptr, Producter, (void *)bq);for(int i=0;i<3;i++)pthread_create(c+i, nullptr, Consumer, (void *)bq);for(int i=0;i<5;i++)pthread_join(p[i], nullptr);for(int i=0;i<3;i++)pthread_join(c[i], nullptr);delete bq;return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/431818.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java 微服务框架 HP-SOA v1.1.4

HP-SOA 功能完备&#xff0c;简单易用&#xff0c;高度可扩展的Java微服务框架。 项目主页 : https://www.oschina.net/p/hp-soa下载地址 : https://github.com/ldcsaa/hp-soa开发文档 : https://gitee.com/ldcsaa/hp-soa/blob/master/README.mdQQ Group: 44636872, 66390394…

ctf.show---->re2

做题笔记。 下载 查壳 32 ida打开。 WSL先运行一下&#xff1a; &#xff1f; 创建呗。 函数如下&#xff1a; 逻辑很清晰&#xff0c;写脚本咯 &#xff1a; #include <stdio.h> #include <string.h> #include <stdlib.h>int main() {char encode[] &qu…

TCP网络编程概述、相关函数、及实现超详解

文章目录 TCP网络编程概述1. TCP协议的特点2. TCP与UDP的差异3. TCP编程流程 TCP网络编程相关函数详解1. socket()&#xff1a;创建套接字参数说明&#xff1a;返回值&#xff1a;示例&#xff1a; 2. connect()&#xff1a;客户端连接服务器参数说明&#xff1a;返回值&#x…

IDEA去除掉虚线,波浪线,和下划线实线的方法

初次安装使用IDEA&#xff0c;总是能看到导入代码后&#xff0c;出现很多的波浪线&#xff0c;下划线和虚线&#xff0c;这是IDEA给我们的一些提示和警告&#xff0c;但是有时候我们并不需要&#xff0c;反而会让人看着很不爽&#xff0c;这里简单记录一下自己的调整方法&#…

Ubunt系统设置NVIDIA显卡性能模式

文章目录 前言一、了解自己的显卡1、输入nvidia-smi指令搞清楚表中的含义 二、通过英伟达官方设置进行修改1、此时的状态3、改完后变为P0 总结 前言 工欲善其事&#xff0c;那性能直接拉满&#xff0c;宁可累坏显卡&#xff0c;也不能影响自己&#xff0c;首先了解自己的显卡以…

OpenAPI鉴权(二)jwt鉴权

一、思路 前端调用后端可以使用jwt鉴权&#xff1b;调用三方接口也可以使用jwt鉴权。对接多个三方则与每个third parth都约定一套token规则&#xff0c;因为如果使用同一套token&#xff0c;token串用可能造成权限越界问题&#xff0c;且payload交叉业务不够清晰。下面的demo包…

uni-app页面调用接口和路由(四)

文章目录 一、路由二、页面调用接口二、路由跳转1.uni.navigateTo(OBJECT)2.uni.redirectTo(OBJECT)3.uni.reLaunch(OBJECT)4.uni.switchTab(OBJECT)5.uni.navigateBack(OBJECT) 总结 一、路由 路由配置 uni-app页面路由为框架统一管理&#xff0c;开发者需要在pages.json里配…

iOS六大设计原则设计模式

六大设计原则&#xff1a; 一、单一职责原则 一个类或者模块只负责完成一个职责或者功能。 类似于&#xff1a;UIView 和 CALayer 二、开放封闭原则 对扩展开放&#xff0c;对修改封闭。 我们要尽量通过扩展软件实体来解决需求变化&#xff0c;而不是通过修改已有的代码来…

ESP32-WROOM-32 [创建AP站点-客户端-TCP透传]

简介 基于ESP32-WROOM-32 开篇(刚买)&#xff0c; 本篇讲的是基于固件 ESP32-WROOM-32-AT-V3.4.0.0&#xff08;内含用户指南, 有AT指令说明&#xff09;的TCP透传设置与使用 设备连接 TTL转USB线, 接ESP32 板 的 GND&#xff0c;RX2&#xff0c; TX2 指令介绍 注意,下面指…

Facebook Marketplace无法使用的原因及解决方案

Facebook Marketplace是一项广受欢迎的买卖平台&#xff0c;然而&#xff0c;有时候用户可能会遇到无法访问或使用该功能的问题。通常&#xff0c;这些问题可以归结为以下几类原因&#xff1a; 地理位置限制&#xff1a; Facebook Marketplace并非在全球每个地区都可用。在某些…

【C++】C++中如何处理多返回值

十四、C中如何处理多返回值 本部分也是碎碎念&#xff0c;因为这些点都是很小的点&#xff0c;构不成一篇文章&#xff0c;所以本篇就是想到哪个点就写哪个点。 1、C中如何处理多个返回值 写过python的同学都知道&#xff0c;当你写一个函数的返回时&#xff0c;那是你想返回…

MySQL(日志)

日志 日志分为三种&#xff1a; undo log &#xff08;回滚日志&#xff09;&#xff1a;用于事务回滚和MVCC redo log &#xff08;重做日志&#xff09;&#xff1a;用于故障恢复 binlog &#xff08;归档日志&#xff09;&#xff1a;用于数据备份和主从复制 undo log undo…

一个简单的个人博客管理平台适合新手学习(最底下有github链接)

一个简单的个人博客管理平台 欢迎大家一起学习 前端技术栈 vue3jshtmlcsselement-plus 后端技术栈 springbootredismysqlmybatis 首页展示 页面介绍 左边为文章界面点击文字名字就能进行阅读 右边为热门博客功能能够实时统计前五名博客热度并且将前三名展示在卡片中(卡片…

【人工智能学习之常用损失函数浅谈】

【人工智能学习之常用损失函数浅谈】 Focal Loss基本概念Focal Loss的定义作用应用场景 Arc Loss基本概念ArcFace Loss的定义作用应用场景 CenterLoss基本概念Center Loss 的定义作用应用场景实现细节 Cross Entropy Loss (CELoss)基本概念二分类任务多分类任务作用优点缺点应用…

oracle avg、count、max、min、sum、having、any、all、nvl的用法

组函数 having的使用 any的使用 all的使用 nvl 从执行结果来看&#xff0c;nvl(列名&#xff0c;默认值)&#xff0c;nvl的作用就是如果列名所在的这一行出现空则用默认值替换

D. Determine Winning Islands in Race (cf div2,dp、图论最短路)

D. Determine Winning Islands in Race 思路: bfs找到E到达每个点的最短时间t[i]。 如果E要超过B&#xff0c;那么一定要借助辅助桥&#xff0c;从而获胜。 假设有u->v的辅助桥&#xff0c;E能通过这个桥超过B的条件是: s>u 且 t[v] < v-s 即 s的取值要为[u1,v-t[v]-…

每天分享一个FPGA开源代码(6)- 浮点数运算

FPGA&#xff08;现场可编程门阵列&#xff09;是一种高度可配置的集成电路&#xff0c;它可以用于实现各种数字信号处理任务&#xff0c;包括浮点数运算。 在FPGA上进行浮点数运算通常涉及以下几个步骤&#xff1a; 1. 选择浮点数格式 浮点数运算首先要确定使用哪种浮点数格…

第二十八篇——用间篇:使用间谍,先学习花钱的价值观

目录 一、背景介绍二、思路&方案三、过程1.思维导图2.文章中经典的句子理解3.学习之后对于投资市场的理解4.通过这篇文章结合我知道的东西我能想到什么&#xff1f; 四、总结五、升华 一、背景介绍 间谍的本质&#xff0c;任何策略没有好坏没有道德&#xff0c;它是平衡路…

【大模型】AutoDL部署AI绘图大模型Stable Diffusion使用详解

目录 一、前言 二、AI绘图大模型概述 2.1 AI绘图大模型介绍 2.2 AI绘图大模型特点 2.3 AI绘图大模型优势 三、主流的AI绘图大模型介绍 3.1 Midjourney 3.1.1 Midjourney介绍 3.1.2 Midjourney功能特点 3.1.3 Midjourney使用场景 3.2 Stable Diffusion 3.2.1 Stable …

Unity中的功能解释(数学位置相关和事件)

向量计算 Vector3.Slerp&#xff08;起点坐标&#xff0c;终点坐标&#xff0c;t&#xff09;&#xff0c;可是从起点坐标以一个圆形轨迹到终点坐标&#xff0c;有那么多条轨迹&#xff0c;那怎么办 Vector3.Slerp 进行的是沿球面插值&#xff0c;因此并不是沿着严格的“圆形…