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

28.线程互斥与同步(二)

POSIX信号量

信号量本质是一个计数器,是对特定资源的预定机制!

多线程使用资源,有两种情况:

1.将目标资源整体使用【mutex+2元信号量】

2.将目标资源按照不同的“块”,分批使用【信号量】

所有的线程,都得看到同一份资源sem,信号量本身也是临界资源。

信号量的P:--,原子的;V:++,原子的

2.CP&基于环形队列的生产者消费者模型

回顾数据结构环形队列:

        逻辑上,环形队列是个圆,大小固定。

对于空和满的判定:

        方案一:int count; 记录当前数据的数量

        方案二:空一格,head和tail相等为空,tail下一个是head为满。

实现方式:

        可以用一个长度固定的数组实现,下标每次++后就 %= 长度,就可以实现回绕。

具体实现:

约定1:空,生产者先运行

约定2:满,消费者先运行

约定3:生产者不能套消费者一圈以上

约定4:消费者不能超过生产者

思考:

1.只要消费者和生产者不在同一位置,就互不干扰,可以并发执行。

2.什么时候消费者和生产者在同一位置?空或满的时候,只要不为空或满就可以同时运行。

3.为空,只能(互斥)生产者先执行(同步,顺序固定)

   为满,只能(互斥)消费者先执行(同步,顺序固定)

因此,当前思路就保证了生产者和消费者之间的:互斥(只能有一个) 和 同步(固定先后)

对于生产者来说,空位置是资源;对于消费者来说,数据是资源

伪代码呈现思路:

        生产者                                                                                 消费者

初始(sem_blank = N, p_step = 0)                                  (sem_data = 0, c_step = 0 )

P(sem_blank);   // 空位置--                                                   P(sem_data);

在p_step的位置生产                                                        ​​​​​​​    在c_step的位置消费

p_step++;                          ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​    c_step++;

p_step %= N;    // 保持环形队列                ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​    c_step %= N;

V(sem_data);   // 数据++                ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​      V(sem_blank);

注:信号量P操作是原子的,申请成功,继续运行,申请失败,申请的线程会被阻塞。

分析整个流程,各个情况,以单生产者,单消费者来看:

情况1:sem_blank为N,sem_data为0,为空的情况

        生产者线程P操作成功,消费者P操作会阻塞,直到生产者V操作把sem_data++。

情况2:sem_blank为0,sem_data为N,为满的情况

        生产者P操作阻塞,消费者P操作成功,直到消费者V操作把sem_blank++。

情况3:sem_blank为x, sem_data为N-x,x>0,其他情况

        两者P操作都可以成功,互不干扰,可以并发执行

总结:

        环形队列用信号量来实现生产者消费者模型的原因:环形队列的长度固定,资源明确划分,资源数量确定。生产者和消费者在进行同一块资源访问时,必有一个信号量为0,要阻塞,等待另一个V操作++,这样就保证了同步和互斥;访问不同块资源时,没有任何限制,可以并发执行。

3.熟悉信号量的接口,编写代码

#include <semaphore.h>

sem_t sem;

初始化:

        sem_init(&sem, 0, value); //第二个参数0表示线程间共享,非0表示进程间共享;第三个参数value表示信号量的初始值

销毁:

        sem_destroy(&sem);

等待信号量(P操作,--):

        sem_wait(&sem);

发布信号量(V操作,++):

        sem_post(&sem);

信号量的封装:

#pragma once
#include <semaphore.h>class Sem
{
public:Sem(unsigned int num){sem_init(&_sem, 0, num);}~Sem(){sem_destroy(&_sem);}void P(){sem_wait(&_sem);}void V(){sem_post(&_sem);}private:sem_t _sem;
};

RingQueue.hpp实现:

#pragma once#include "Sem.hpp"
#include <vector>
#include "Mutex.hpp"using namespace MutexModule;
static const unsigned int defaultnum = 5;template<typename T>
class RingQueue
{
public:RingQueue(unsigned int cap = defaultnum): _v(cap, 0), _cap(cap), _pstep(0), _cstep(0), _sem_blank(cap), _sem_data(0){}~RingQueue(){}void EnQueue(const T &data){// 生产者_sem_blank.P();{// 先竞争信号量,再竞争锁,效率更高MutexGuard mutexguard(_pmutex);// 1.执行_pstep位置的任务_v[_pstep] = data;// 2.迭代_pstep++;// 3.维持环形队列结构_pstep %= _cap;}_sem_data.V();}void Pop(T *data){// 消费者_sem_data.P();{MutexGuard mutexguard(_cmutex);*data = _v[_cstep];_cstep++;_cstep %= _cap;}_sem_blank.V();}private:std::vector<T> _v; // 数组模拟环形队列int _cap;            // 环形队列容量int _pstep; // 生产者下标int _cstep; // 消费者下标Sem _sem_blank; // 空位置信号量,生产者资源Sem _sem_data;  // 数据信号量,消费者资源Mutex _pmutex; // 生产者间的互斥锁Mutex _cmutex; // 消费者间的互斥锁
};

注意的点:信号量只能保证生产者和消费者之间的互斥和同步关系。

为了实现多生产者和多消费者,需要实现生产者间的互斥关系,消费者间的互斥关系。

锁的申请要在申请信号量之后,原因:

1)信号量的PV操作是原子的,不用锁保护

2)先竞争信号量,再竞争锁,效率更高 -> 先申请信号量,线程是并行执行的,可以让线程先持有资源,在申请锁成功可以直接执行临界区代码;若是先申请锁,那么每次只能有一个线程去竞争信号量,其他线程在锁释放前什么也做不了,效率低下 -> 这也是锁要尽可能细的加在临界区之间,尽量不要包含非临界区代码的原因


文章转载自:

http://sE06zZke.jqsyp.cn
http://mh2N8sFo.jqsyp.cn
http://4njD7bhK.jqsyp.cn
http://k6jKoVpA.jqsyp.cn
http://nnWUGelK.jqsyp.cn
http://ONWGFBji.jqsyp.cn
http://3aZ5Q9IG.jqsyp.cn
http://F1BsQUom.jqsyp.cn
http://bHvGnKcO.jqsyp.cn
http://FHq9Phcj.jqsyp.cn
http://FcfWPWT4.jqsyp.cn
http://tXbTIUc4.jqsyp.cn
http://kQlYGrrt.jqsyp.cn
http://9ZtQJWWu.jqsyp.cn
http://MJZRVYVC.jqsyp.cn
http://Kf04QUUO.jqsyp.cn
http://KKQtDtdl.jqsyp.cn
http://kf3hmrJ1.jqsyp.cn
http://BLNktRJb.jqsyp.cn
http://2Y4euJsd.jqsyp.cn
http://oiZpwYQT.jqsyp.cn
http://ycml6iR1.jqsyp.cn
http://no5LdrId.jqsyp.cn
http://Q7E2WAQG.jqsyp.cn
http://WctLo5cG.jqsyp.cn
http://GHEYMMqm.jqsyp.cn
http://pD7MlbxV.jqsyp.cn
http://WD5r1h2J.jqsyp.cn
http://dPpVAAhp.jqsyp.cn
http://HlibK6WH.jqsyp.cn
http://www.dtcms.com/a/379137.html

相关文章:

  • 批量修改图片尺寸大小的免费工具
  • 【vscode】如何离线下载vsxi插件,且在无网环境下离线安装插件-2026最新实验教程
  • 基于浏览器运行的本地大模型语音助手
  • 动态热机械分析测试(DMA):解析材料的粘弹性能
  • 【龙智Atlassian插件】Confluence周报插件上线AI智能总结,一键生成专业报告
  • 因表并行引发的血案【故障处理案例】
  • 实现双向循环链表
  • Flutter Riverpod 3.0 发布,大规模重构下的全新状态管理框架
  • This is Game
  • Git分支管理:从创建到合并冲突解决(二)
  • Elasticsearch 7.15 存储类型详解
  • 深入解析数据结构之栈及其应用
  • (一)昇腾AI处理器技术
  • BUUCTF刷题十一道(14)
  • Linux防火墙-Iptables
  • python访问基于docker搭建的elasticsearch
  • logback-spring.xml文件说明
  • 【PyTorch训练】为什么要有 loss.backward() 和 optimizer.step()?
  • 抖音大数据开发一面(0905)
  • 原生js的轮播图
  • 连接池项目考点
  • ruoyi-flowable-plus框架节点表单的理解
  • js.228汇总区间
  • BERT中文预训练模型介绍
  • 光平面标定建立激光点与世界坐标的对应关系
  • Jmeter执行数据库操作
  • 基于FPGA的图像中值滤波算法Verilog开发与开发板硬件测试
  • 微软Aurora大模型实战:五大数据源驱动、可视化对比与应用
  • 【论文笔记】SpaRC: Sparse Radar-Camera Fusion for 3D Object Detection
  • C++基本数据类型的范围