当前位置: 首页 > news >正文

【Linux学习笔记】线程同步与互斥之生产者消费者模型

【Linux学习笔记】线程同步与互斥之生产者消费者模型

在这里插入图片描述

🔥个人主页大白的编程日记

🔥专栏Linux学习笔记


文章目录

  • 【Linux学习笔记】线程同步与互斥之生产者消费者模型
    • 前言
  • 2. 线程同步
  • 2-1条件变量
  • 2-2 同步概念与竞态条件
  • 2-3条件变量函数
  • 简单案例:
  • 2-4生产者消费者模型
  • 2-4-1 为何要使用生产者消费者模型
  • 2-4-2 生产者消费者模型优点
  • 2-5基于BlockingQueue的生产者消费者模型
  • 2-5-1 BlockingQueue
  • 2-5-2 C++ queue模拟阻塞队列的生产消费模型
  • BlockQueue.hpp
  • 2-6 为什么 pthread_cond_wait 需要互斥量?
  • 2-7条件变量使用规范
  • 2-8条件变量的封装
    • 后言

前言

哈喽,各位小伙伴大家好!上期我们讲了线程同步与互斥(一) 今天我们讲的是线程同步与互斥之生产者消费者模型。话不多说,我们进入正题!向大厂冲锋!
在这里插入图片描述

2. 线程同步

在这里插入图片描述

2-1条件变量

  • 当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。
  • 例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。
    在这里插入图片描述

2-2 同步概念与竞态条件

  • 同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步
  • 竞态条件:因为时序问题,而导致程序异常,我们称之为竞态条件。在线程场景下,这种问题也不难理解

2-3条件变量函数

初始化

1 intPTHread_cond_init(pthread_cond_t \*restrict cond,constPTHread_condattr_t \*restrict attr);  
2 参数:  
3 cond: 要初始化的条件变量  
4 attr: NULL

销毁

1 intPTHread_cond destroy(pthread_cond_t \*cond)

等待条件满足

1 int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);  
2 参数:  
3 cond: 要在这个条件变量上等待  
4 mutex: 互斥量, 后面详细解释
1 int pthread_cond_broadcast(pthread_cond_t *cond);
2 int pthread_cond_signal(pthread_cond_t *cond);

简单案例:

  • 我们先使用PTHREAD_COND/MUTEX_INITIALIZER进行测试,对其他细节暂不追究
  • 然后将接口更改成为使用 pthread_cond_init/pthread_cond_destroy 的方式,方便后续进行封装
#include <iostream>   
#include <string.h>   
#include <unistd.h>   
#include <pthread.h>   
pthread_cond_t cond = PTHREAD_COND Initialized;   
pthread_mutex_t mutex = PTHREAD_MUTEX Initialized;   
void *active(void *arg)   
{   std::string name = static_cast(const char*>(arg);   while (true) {   pthread_mutex_lock(&_mutex);   pthread_cond_wait(&cond, &_mutex);   std::cout << name << "活动..." << std::endl;   pthread_mutex_unlock(&_mutex);   
}   
}   
int main(void)   
{   pthread_t t1, t2;   pthread_create(&t1, NULL, active, (void*)"thread-1");   pthread_create(&t2, NULL, active, (void*)"thread-2");   sleep(3); //可有可无,这里确保两个线程已经在运行  while(true)   {   //对比测试   //pthread_cond_signal(&cond); //唤醒一个线程   pthread_cond_broadcast(&cond); //唤醒所有线程   sleep(1);   
}pthread_join(t1, NULL);pthread_join(t2, NULL);}
1 $ ./cond
2 thread-1 活动...
3 thread-2 活动...
4 thread-1 活动...
5 thread-1 活动...
6 thread-2 活动...

在这里插入图片描述

在这里插入图片描述

2-4生产者消费者模型

321原则(便于记忆)
在这里插入图片描述

2-4-1 为何要使用生产者消费者模型

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。

2-4-2 生产者消费者模型优点

  • 解耦
  • 支持并发
  • 支持忙闲不均

在这里插入图片描述

2-5基于BlockingQueue的生产者消费者模型

2-5-1 BlockingQueue

在多线程编程中阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构。其与普通的队列区别在于,当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素;当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出(以上的操作都是基于不同的线程来说的,线程在对阻塞队列进程操作时会被阻塞)

2-5-2 C++ queue模拟阻塞队列的生产消费模型

代码:

  • 我们以单生产者,单消费者,来进行讲解。
    • 刚开始写,我们采用原始接口。
  • 我们先写单生产,单消费。然后改成多生产,多消费(这里代码其实不变)。

BlockQueue.hpp

#pragma once
#include <pthread.h>
#include <iostream>
#include <string>
#include <queue>
using namespace std;const int defaultcap = 5; // for test
template <class T>
class BlockQueue
{
public:bool IsFull(){return _cap <= _q.size();}bool IsEmpty(){return _q.size() == 0;}BlockQueue(int cap = defaultcap): _cap(cap), _csleep_num(0), _psleep_num(0){pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_full_cond, nullptr);pthread_cond_init(&_empty_cond, nullptr);}~BlockQueue(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_full_cond);pthread_cond_destroy(&_empty_cond);}T Pop(){pthread_mutex_lock(&_mutex);// 避免休眠失败向后直接pop 进行二次检查!while (IsEmpty()){// 队列为空消费者去条件变量下休眠_csleep_num++;pthread_cond_wait(&_empty_cond, &_mutex);_csleep_num--;}T ret = _q.front();_q.pop();// 走到这一定有生产空间 唤醒生产者生产if (_psleep_num){pthread_cond_signal(&_full_cond);cout << "唤醒一个生产者" << endl;}pthread_mutex_unlock(&_mutex);return ret;}void Equeue(const T& date){pthread_mutex_lock(&_mutex);// 避免休眠失败向后直接push 进行二次检查!while (IsFull()){cout<<"生产者进行休眠了!:"<<_psleep_num<<endl;// 队列为满生产者去条件变量下休眠_psleep_num++;pthread_cond_wait(&_full_cond, &_mutex);_psleep_num--;}_q.push(date);// 走到这一定有消费空间 唤醒消费者生产if (_csleep_num){pthread_cond_signal(&_empty_cond);cout << "唤醒一个消费者" << endl;}pthread_mutex_unlock(&_mutex);pthread_cond_signal(&_empty_cond);}private:std::queue<T> _q; // 临界资源!!!int _cap;         // 容量大小pthread_mutex_t _mutex;pthread_cond_t _full_cond;pthread_cond_t _empty_cond;int _csleep_num; // 消费者休眠的个数int _psleep_num; // 生产者休眠的个数
};

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

注意:这里采用模版,是想告诉我们,队列中不仅仅可以防止内置类型,比如int,对象也可以作为任务来参与生产消费的过程哦。

1 #pragma once   
2   
3 #include<iostream>   
4 #include<string>   
5 #include<functional>   
6   
7 //任务类型1   
8 //class Task   
9 //{   
10 //public:   
11 //Task(){}   
12 //Task(int a,int b):_a(a),_b(b),_result(0)
13 // {  
14 // }  
15 // void Excute()  
16 // {  
17 // _result = _a + _b;  
18 // }  
19 // std::string ResultToString()  
20 // {  
21 // return std::to_string(_a) + "+" + std::to_string(_b) + "=" + std::to_string(_result);  
22 // }  
23 // std::string DebugToString()  
24 // {  
25 // return std::to_string(_a) + "+" + std::to_string(_b) + "=";  
26 // }  
27  
28 // private:  
29 // int _a;  
30 // int _b;  
31 // int _result;  
32 // };  
33  
34 // 任务类型2  
35 using Task = std::function<void>();

2-6 为什么 pthread_cond_wait 需要互斥量?

  • 条件等待是线程间同步的一种手段,如果只有一个线程,条件不满足,一直等下去都不会满足,所以必须要有一个线程通过某些操作,改变共享变量,使原先不满足的条件变得满足,并且友好的通知等待在条件变量上的线程。
  • 条件不会无缘无故的突然变得满足了,必然会牵扯到共享数据的变化。所以一定要用互斥锁来保护。没有互斥锁就无法安全的获取和修改共享数据。

在这里插入图片描述

互斥量

被锁挡住了没办法执行

造成死锁

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 按照上面的说法,我们设计出如下的代码:先上锁,发现条件不满足,解锁,然后等待在条件变量上不就行了,如下代码:
// 错误的设计
pthread_mutex_lock(&mutex);
while (condition_is_false) {pthread_mutex_unlock(&mutex);// 解锁之后,等待之前,条件可能已经满足,信号已经发出,但是该信号可能被错过pthread_cond_wait(&cond, &mutex);pthread_mutex_lock(&mutex);
}
pthread_mutex_unlock(&mutex);
  • 由于解锁和等待不是原子操作。调用解锁之后,pthread_cond_wait 之前,如果已经有其他线程获取到互斥量,摒弃条件满足,发送了信号,那么pthread_cond_wait 将错过这个信号,可能会导致线程永远阻塞在这个pthread_cond_wait。所以解锁和等待必须是一个原子操作。
  • intPTHread_cond_wait(pthread_cond_t \*cond,pthread_mutex_t\* mutex);进入该函数后,会去看条件量等于0不?等于,就把互斥量变成1,直到cond_wait返回,把条件量改成1,把互斥量恢复成原样。

2-7条件变量使用规范

等待条件代码

1 pthread_mutex_lock(& mutex);   
2 while(条件为假)//if??   
3 pthread_cond_waitcond,mutex);   
4 修改条件   
5 pthread_mutex_unlock(&mutex);
  • 给条件发送信号代码
1 pthread_mutex_lock(& mutex);   
2 设置条件为真   
3 pthread_cond_signalcond);   
4 pthread_mutex_unlock(& mutex);

2-8条件变量的封装

  • 基于上面的基本认识,我们已经知道条件变量如何使用,虽然细节需要后面再来进行解释,但这里可以做一下基本的封装,以备后用。
Cond.hpp
#include <pthread.h>
#include <iostream>
using namespace std;
#include"Mutex.hpp"
using namespace MutexModule;
namespace CondModule
{class Cond{public:Cond(){pthread_cond_init(&_cond,nullptr);}~Cond(){pthread_cond_destroy(&_cond);}void Wait(Mutex& mutex){pthread_cond_wait(&_cond,mutex.Get());}void Signal(){pthread_cond_signal(&_cond);}void Brodcast(){pthread_cond_broadcast(&_cond);}private:pthread_cond_t _cond;};
}

后言

这就是线程同步与互斥之生产者消费者模型。大家自己好好消化!今天就分享到这! 感谢各位的耐心垂阅!咱们下期见!拜拜~

http://www.dtcms.com/a/502801.html

相关文章:

  • C++函数使用
  • 立创EDA专业版使用技巧——全部框选与部分框选
  • yii2添加新的modules完为什么访问的时候报错404
  • HTML 头部
  • 内存流 + NPOIExcel, 读取Excel单元格内容
  • 文件包含漏洞全解析:从原理到实战
  • 【深度学习新浪潮】天数天算、地数天算与天地同算:概念解析与SOTA解决方案
  • 《C++ Web 自动化测试实战:常用函数全解析与场景化应用指南》
  • 在线做数据图的网站网站建设 鸿
  • K8s HTTPS流量管理实战:GatewayAPI指南
  • stable-diffusion-webui 安装环境
  • 【Linux】基础IO(二)深入理解“一切皆文件” 与缓冲区机制:从原理到简易 libc 实现
  • 键值存储分解技术在物联网场景中的优化
  • 企业电子商务网站建设的重要性2021最新域名id地址
  • 【C++】二叉搜索树(图码详解)
  • MySQL:14.mysql connect
  • 建设工程消防信息网站网站开发全程实例课本代码
  • 用excel绘制茎叶图
  • 龙岗优化网站建设门户网站建设需要多少
  • 东莞seo网站制作报价网站有域名没备案
  • 网站开发框架图自建wordpress主题
  • 网站公司模板多少工资
  • 江西建设厅网站dede手机网站模板哦
  • 网站运维平台建设原则西乡网站的建设
  • 帮网站网站做推广被抓会判刑吗凡科做的网站百度不到
  • 一台服务器怎么做多给网站wordpress用户投稿
  • 提供免费主页空间的网站中石油第七建设公司网站
  • 顺德网站建设基本流程高柏企业管理咨询有限公司
  • 深圳公司建立网站网络建设专业石家庄
  • 基于jsp的电商网站开发wordpress模板带小程序源码