Linux:多线程---深入生产消费模型&&环形队列生产消费模型

Linux:多线程---深入生产消费模型&&环形队列生产消费模型

文章目录

  • 序:在上一章中,我们通过同步的概念了解了条件变量的概念,并且对生产者消费者模型有了一定的认识,但仅仅是这样,我们对于生产者消费者模型的认识还是太浅显了,所以本章将深入生产者消费者模型,并用POSIX信号量来实现环形队列生产者消费者模型

1. 生产者消费者模型

1.1 深入生产消费模型

问题一:生产者的数据从哪里来?

用户,网络等,生产者生产的数据也是要花时间的!
消费者要不要对拿到的数据进行数据加工处理?要的,这也是要花时间的。
生产者:
1. 获取数据
2.生产数据到队列
消费者:
1. 消费数据
2. 对数据进行加工处理

问题二:生产消费模型为什么高效?生产消费模型,不是生产几个,然后消费几个吗?哪怕有不同的生产和消费策略,但是这哪里高效了?

当生产者持有锁时,他在生产数据,此时消费者难道就只能等待生产者生产数据到一个临界点,甚至生产满了吗?答案显然不是,消费者可以在未持有锁的阶段去将之前消费的数据进行加工处理呀!!!同理,当消费者持有锁时,生产者也不会傻傻的等待消费者将数据消费完,生产者会去获取数据!!!等到生产者持有锁时,再接着生产数据到队列内!!!所以这才是生产消费模型高效的原因,无论是生产者还是消费者,他们每时每刻都在完成任务,将空闲的时间利用上,这就是高效!!!
1.2 条件变量误唤醒

问题一:如果线程wait时,被误唤醒了呢??

假设此时最多能够容纳10个资源,现在已经有9个了,如果调用pthread_cond_broadcast函数唤醒全部线程,三个线程就会对锁展开竞争,其中一个线程就会持有锁,然后生产一个资源后再释放锁,而另外两个线程此时已经不在等待队列了,而是在锁的队列下进行等待,此时锁被释放,两个线程中的其中一个就会得到锁,继续执行生产资源的任务,然后此时的资源已经满了(这种情况就叫做伪唤醒或者误唤醒),此时的另外两个线程,拿到锁后,并不是从位置1开始进行的,而是直接从位置2开始执行代码,这就会导致没有进行判断这一步,因为多个线程被唤醒后,这些线程就不在条件变量下等待了,所以访问的有序性也就没了。
而要解决这个问题,也很简单,只需要将if的判断条件换成while,这样即使是有伪线程,也会让伪线程再次进行判断,然后再次进入等待队列中进行等待。

2. POSIX信号量

2.1 信号量的概念
POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。 但POSIX可以用于线程间同步。
2.2 信号量的接口

初始化信号量:

对信号量进行初始化

第一个参数:传入系统定义的数据结构sem_t
第二个参数:是否为线程间共享,直接默认传0
第三个参数:表示信号量的数量(本质上是计数器)

销毁信号量:

释放信号量资源

第一个参数:直接传系统定义的数据结构sem_t

等待信号量:

获取信号量资源,对应信号量进行减减操作 — P操作

这里只讨论sem_wait函数,第一个参数直接传系统定义的数据结构sem_t

发布信号量:

释放信号量资源,对信号量进行加加操作 — V操作

第一个参数直接传系统定义的数据结构sem_t
信号量的使用:p()操作 —> //访问资源 —> V()操作 —> //释放资源

3. 环形队列生产消费模型

3.1 环形队列的概念
用数组模拟环形队列,有tail和head,head位置是添加元素的位置,满了和空的时候,tail和head指向的是同一个位置,所以无法判断是空还是满,第一种方法是添加计数器,第二种方法是空一个位置,加个判断temp=head+1;temp%20;判断head的下一个位置是不是tail,如果是就生产满了,就不允许生产了,反之则可以生产

其中消费者和生产者都是顺时针进行生产和消费,并且满足以下规则:

1. 指向同一个位置,只能由一个人访问(空:只能由生产者来访问环形队列,满:只能由消费者来访问环形队列)
2. 消费者不能超过生产者
3. 生产者不能把消费者套一个圈
只有当空和满的时候,生产者和消费者才会在同一个位置,当不空和不满的时候,生产者和消费者就不在同一个位置。
生产者关注什么资源?关注环形队列还有多少剩余空间(最开始SpaceSem:N)
消费者关注什么资源?关注环形队列还有多少剩余数据(最开始DataSem:0)
环形队列代码如下:
#pragma once #include<iostream> #include<vector> #include<semaphore.h> template<class T> class RingQueue { const static int defuatcap = 50; private: void P(sem_t &sem) { sem_wait(&sem); } void V(sem_t &sem) { sem_post(&sem); } void Lock(pthread_mutex_t &mutex) { pthread_mutex_lock(&mutex); } void Unlock(pthread_mutex_t &mutex) { pthread_mutex_unlock(&mutex); } public: RingQueue(int cap = defuatcap) :_ringqueue(cap) ,_cap(cap) ,_c_step(0) ,_p_step(0) { pthread_mutex_init(&_c_mutex,nullptr); pthread_mutex_init(&_p_mutex,nullptr); sem_init(&_c_data_sem,0,0); sem_init(&_p_space_sem,0,_cap); } void Push(const T &in)//生产 { P(_p_space_sem); Lock(_p_mutex); _ringqueue[_p_step] =in; //位置后移,维持环形特征 _p_step++; _p_step%=_cap; Unlock(_p_mutex); V(_c_data_sem); } void Pop(T *out)//消费 { P(_c_data_sem); Lock(_c_mutex); *out = _ringqueue[_c_step]; //位置后移,维持环形特征 _c_step++; _c_step%=_cap; Unlock(_c_mutex); V(_p_space_sem); } ~RingQueue() { pthread_mutex_destroy(&_c_mutex); pthread_mutex_destroy(&_p_mutex); sem_destroy(&_c_data_sem); sem_destroy(&_p_space_sem); } private: std::vector<T> _ringqueue; int _cap; int _c_step;//消费者下标 int _p_step;//生产者下标 sem_t _c_data_sem;//消费者关注的数据资源 sem_t _p_space_sem;//生产者关注的空间资源 pthread_mutex_t _p_mutex; pthread_mutex_t _c_mutex; }; 
当环形队列为空时,只能进行生产动作,不能进行消费动作
当环形队列为满时,只能进行消费动作,不能进行生产动作
当环形队列不空不满时,生产者和消费者就实现了并发访问,能够同时进行
其中,对于先加锁还是先申请信号量,有两种方案:
方案一

方案二
首先,信号量的操作是原子的,是不需要加锁的,临界区代码能少就少,其次,如果先申请锁再申请信号量,如果信号量没有申请到,就会白费时间,而如果先进行申请信号量再申请锁,假设,有一个线程已经申请到锁了,正在进行生产,此时其他线程进来了,就不会干等待,等待锁的就绪,而是会先申请信号量,如果申请到了,只要锁一释放就能立马进行生产操作!!!,只有抢到了信号量资源,才需要被加锁,如果连信号量资源都没有申请到,就没必要加锁,加锁也是很耗资源的,所以选择第二个方案。
通俗点讲,如果将信号量比作票数,锁比作一个门,如果先排队,到门口再进行买票,就会出现排了半天发现没票了,进不去门里面,浪费了时间,而如果先买了票,让买了票的人进行排队,只要排到了就能立马进入门里面。
当环形队列不为空和不为满的时候,生产者和消费者只需要维持自身的互斥关系,不需要考虑对方。

总结:

本章深入探讨了生产者消费者模型,引入了生产者获取生产资料和消费者对消费数据进行加工的概念,而后又通过信号量实现了环形队列下的生产消费模型,深入探讨了加锁和PV操作的优先级的问题。

Read more

Git 结合 Gitee 使用教程:从入门到实战

Git 结合 Gitee 使用教程:从入门到实战

目录 一、Git 与 Gitee 简介 二、环境搭建 三、Git 基础操作(本地仓库) 四、Gitee 平台使用 Git 与 Gitee 的关系详解 核心关系:工具 vs 平台 两者如何协同工作: 五、本地仓库与 Gitee 关联 六、日常开发流程(单人) 七、分支管理 八、团队协作实战 九、实战练习建议 十、常见问题 附录、常用命令速查表 一、Git 与 Gitee 简介 我们为什么要用git? 用 Git 就是为了方便管代码、

By Ne0inhk
GitHub 学生认证申请流程与常见问题(实测经验分享)

GitHub 学生认证申请流程与常见问题(实测经验分享)

通过后效果展示 完成 GitHub 学生认证后,可在 GitHub 官网使用学生包内相关开发资源,并可在 VS Code 中启用(如 Copilot 等符合政策的功能),有助于学习与代码编写。 申请认证流程: 1.注册登录Github网站         找到学生认证 入口。 2.绑定并验证学校邮箱         申请过程会让你使用绑定你的学校邮箱并验证 3.开启 2FA(双因素认证)         该步需通过浏览器安装插件,Edge浏览器在扩展中搜索:身份验证器插件         过程中其他步骤参考该博客即可:Enable two-factor authentication (2FA) -github解决方案 提醒:生成的密钥 / 恢复代码一定要妥善保存,丢失会给后续登录带来麻烦。!!!         按教程一般能顺利到达输入验证这一步,选择第一项,使用你电脑先前设置的 PIN 即可。 4.提交证明材料         证明类型选择第 1 项:

By Ne0inhk

tmux_for_windows windows上面安装 git bash 2026年 正是专门为了解决“SSH 掉线后还能继续原来的会话”这个问题而设计的工具

tmux_for_windows tmux是一个开源工具,用于在一个终端窗口中运行多个终端会话。本工具从msys2里提取,可以在Git for Windows的Git Bash (MingW64)下正常使用。 蘭雅sRGB 龙芯小本服务器 | https://262235.xyz ##tmux(以及 screen)正是专门为了解决“SSH 掉线后还能继续原来的会话”这个问题而设计的工具。 简单来说: 工具SSH 掉线后还能连上原来的会话吗?说明普通 bash / zsh不能会话结束,进程收到 SIGHUP 信号,通常被杀死tmux能会话独立于 SSH 连接存在,掉线后可以随时重新 attachscreen能和 tmux 功能几乎一样,但 tmux 现在更流行、功能更强zellij能更现代的替代品,但普及度不如 tmux tmux 的典型用法(SSH 掉线后重连)

By Ne0inhk
HarmonyOS 开源实战:动态轨道生成 —— 实现“点击延伸轨道”的随机路径系统

HarmonyOS 开源实战:动态轨道生成 —— 实现“点击延伸轨道”的随机路径系统

个人主页:ujainu 文章目录 * 引言 * 一、为什么需要动态轨道生成? * 二、定义 CircleSegment 类 * 三、使用 Math.random() 生成随机方向和距离 * 1. 随机距离:控制可玩性 * 2. 随机方向:引入角度扰动 * 3. 计算新坐标 * 四、边界反弹算法 * 五、防止重叠:isTooClose 碰撞检测 * 重试机制 + Fallback * 六、完整可运行代码(适配 API 6.0.2) * 七、关键技术总结(适配 API 6.0.2) * 八、结语 引言 在跑酷类、节奏跳跃类或几何闯关游戏中,

By Ne0inhk