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

[linux仓库]多线程同步:基于POSIX信号量实现生产者-消费者模型[线程·柒]

🌟 各位看官好,我是egoist2023!

🌍 Linux == Linux is not Unix !

🚀 今天来学习Linux的System V信号量,基于该信号量实现生产者消费者模型的代码。

👍 如果觉得这篇文章有帮助,欢迎您一键三连,分享更多人哦!

目录

回顾System V信号量

认识信号量接口

环形队列

单单CP场景

代码实现

生产数据

消费数据

多多CP场景

总结


回顾System V信号量

还记得这张图不?这是当年在讲进程通信时候,通过讲述电影院的故事,画出的图.上一节我们又有了阻塞队列的知识储备.

阻塞队列当成整体使用,如果此时拆分成一个个小资源呢?让不同的线程访问同一块资源的不同部分,那么不就相当于允许多个线程并发访问同一块资源了吗

整体使用就是要有互斥能力;

局部使用,访问错了,分多资源应该要规避

  •  放过多线程进入,本质就是访问信号量,而信号量本质是一把计数器!(信号量就是一个计数器,可以理解为锁+整数) --> 那么该如何表示信号量还剩多少资源,资源被申请多少了呢? PV操作

要申请信号量 --> 前提是看到同一个信号量 --> 信号量本身就要是共享资源 --> 如何保证自己的安全? --> PV操作是原子性的

  •  让不同的线程,访问同一块资源的不同部分,这个资源在哪里呢?需要让程序员做!

POSIX信号量

POSIX信号量和SystemV信号量作⽤相同,都是⽤于同步操作,达到⽆冲突的访问共享资源⽬的。但POSIX可以⽤于线程间同步。

认识信号量接口

int sem_init(sem_t *sem, int pshared, unsigned int value);

功能:初始化信号量

参数:

        pshared:0表⽰线程间共享,⾮零表⽰进程间共享

        value:信号量初始值

int sem_destroy(sem_t *sem);

功能:销毁信号量


int sem_wait(sem_t *sem); P操作

功能:等待信号量,会将信号量的值减1

int sem_post(sem_t *sem); V操作

功能:发布信号量,表⽰资源使⽤完毕,可以归还资源了。将信号量值加1。

上⼀节⽣产者-消费者的例⼦是基于queue的,其空间可以动态分配,现在基于固定⼤⼩的环形队列重写这个程序(POSIX信号量):

环形队列

单单CP场景

我们现在有信号量这个计数器,就很简单的进⾏多线程间的同步过程。

  • 为空的时候,必须让生产者先运行 --> 生产和消费会访问同一个位置 --> 互斥放入数据 --> 这不就是生产和消费者的互斥与同步关系!
  • 为满的时候,必须让消费者先运行 --> 生产和消费又指向同一个位置了! --> 互斥的获取数据      --> 这不就是生产和消费者的互斥与同步关系!
  • 不为空 && 不为满:此时首 != 尾 ,访问的一定不是同一个位置! --> 此时,不就可以并发执行了?!

代码实现

信号量Sem.hpp封装

class Sem
{
public:Sem(int initnum) : _initnum(initnum){sem_init(&_sem, 0, _initnum);}void P(){int n = sem_wait(&_sem);(void)n;}void V(){int n = sem_post(&_sem);(void)n;}~Sem(){sem_destroy(&_sem);}private:sem_t _sem;int _initnum;
};

RingQueue.hpp

static int gcap = 5; // for debugtemplate <typename T>
class RingQueue
{
public:RingQueue(int cap = gcap): _cap(cap), _ring_queue(cap), _space_sem(cap), _data_sem(0), _p_step(0), _c_step(0){}private:std::vector<T> _ring_queue; // 临界资源int _cap;Sem _space_sem;Sem _data_sem;// 生产和消费的位置int _p_step;int _c_step;
};
生产数据

因为是单生产单消费:

  1. 不担心同时有两个生产者来访问;
  2. 生产者访问期间,消费者不可能来打扰我,一定不为满,最多也就是不为空&不为满,二者可以并发执行,消费者并不影响生产者
    void Enqueue(const T &in){_space_sem.P();{// 生产数据了!有空间,在哪里啊??_ring_queue[_p_step++] = in;// 维持环形特点_p_step %= _cap;}_data_sem.V();}
消费数据

只要消费者走到了*out = _ring_queue[_c_step++,证明队列一定不为空.

    void Pop(T *out){_data_sem.P();{*out = _ring_queue[_c_step++];_c_step %= _cap;}_space_sem.V();}

多多CP场景

如果是多生产多消费呢?就还需要维护生产者之间、消费者之间的互斥关系,那要加几把锁呢?一把锁就放弃了让生产者和消费者并发运行的情况,降低效率.引入两把锁,生产者之间竞争这把锁,消费者之间竞争者这把锁,本质还是归宿到单生产单消费了

static int gcap = 5; // for debugtemplate <typename T>
class RingQueue
{
public:RingQueue(int cap = gcap): _cap(cap), _ring_queue(cap), _space_sem(cap), _data_sem(0), _p_step(0), _c_step(0){}void Pop(T *out){_data_sem.P();{LockGuard lockguard(&_c_lock);*out = _ring_queue[_c_step++];_c_step %= _cap;}_space_sem.V();}void Enqueue(const T &in){_space_sem.P();{LockGuard lockguard(&_p_lock);// 生产数据了!有空间,在哪里啊??_ring_queue[_p_step++] = in;// 维持环形特点_p_step %= _cap;}_data_sem.V();}~RingQueue(){}private:std::vector<T> _ring_queue; // 临界资源int _cap;Sem _space_sem;Sem _data_sem;// 生产和消费的位置int _p_step;int _c_step;// 两把锁Mutex _p_lock;Mutex _c_lock;
};

main.cc

// 基于信号量形成的生产者消费者模型
void *consumer(void *args)
{RingQueue<int> *rq = static_cast<RingQueue<int> *>(args);while (true){int data = 0;rq->Pop(&data);std::cout << "消费了一个数据: " << data << std::endl;}
}void *productor(void *args)
{RingQueue<int> *rq = static_cast<RingQueue<int> *>(args);int data = 1;while (true){sleep(1);rq->Enqueue(data);std::cout << "生产了一个数据: " << data << std::endl;data++;}
}int main()
{RingQueue<int> *rq = new RingQueue<int>();pthread_t c[2], p[3];pthread_create(c, nullptr, consumer, (void *)rq);pthread_create(c + 1, nullptr, consumer, (void *)rq);pthread_create(p, nullptr, productor, (void *)rq);pthread_create(p + 1, nullptr, productor, (void *)rq);pthread_create(p + 3, nullptr, productor, (void *)rq);pthread_join(c[0], nullptr);pthread_join(c[1], nullptr);pthread_join(p[0], nullptr);pthread_join(p[1], nullptr);pthread_join(p[2], nullptr);delete rq;return 0;
}

总结

本文介绍了Linux系统中信号量的概念及其在多线程编程中的应用。首先回顾了SystemV信号量的工作原理,通过计数器机制实现资源共享。然后详细讲解了POSIX信号量接口(sem_init、sem_wait等)及其在线程同步中的使用方法。文章重点演示了如何基于环形队列实现生产者-消费者模型,包括单生产单消费和多生产多消费场景的实现方案,并提供了完整的代码示例。最后,针对多线程环境提出了使用两把锁的解决方案,以平衡并发性能与线程安全性。

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

相关文章:

  • Linux 内核驱动加载机制
  • C语言编译软件 | 高效选择适合的C语言编译环境
  • 天津 网站策划微信、网站提成方案点做
  • 工业级部署指南:在西门子IOT2050(Debian 12)上搭建.NET 9.0环境与应用部署(进阶篇)
  • 食品网站建设网站定制开发做网站只买一个程序
  • 中小型项目前后端工时对比
  • C# 文件的输入与输出
  • Linux操作系统学习
  • idea创建javaweb项目
  • 【计网】基于OSPF 协议的局域网组建
  • 开发一个小程序花多少钱
  • Ansible入门详解
  • 一体化系统(一)智慧物业管理综合管理——东方仙盟
  • 买虚机送网站建设wordpress google ad
  • 2008 iis配置网站公司做网站需要注意些什么问题
  • vs2013编译C语言 | 探讨如何使用Visual Studio 2013进行C语言编译与调试
  • k8s上分离集群seatunnel部署(生产推荐)
  • 最新版idea2025 配置docker 打包spring-boot项目到生产服务器全流程,含期间遇到的坑
  • Python 处理 CSV 和 Excel 文件的全面指南
  • 小程序 scroll-view 触底事件不触发问题
  • word内输入带框打对号的数据
  • C语言编译器软件 | 深入了解编译过程与优化技巧
  • Spring框架 - 声明式事务管理
  • html淘宝店铺网站模板辽宁移动网站
  • 微硕WST3404高性能MOSFET,革新汽车雨刮控制系统
  • LeetCode(python)——53.最大子数组的和
  • 其中包含了三种排序算法的注释版本(冒泡排序、选择排序、插入排序),但当前只实现了数组的输入和输出功能。
  • macOS安装SDKMAN
  • LeetCode热题100--78. 子集
  • 攻击链重构的技术框架