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

[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 debug template <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 debug template <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等)及其在线程同步中的使用方法。文章重点演示了如何基于环形队列实现生产者-消费者模型,包括单生产单消费和多生产多消费场景的实现方案,并提供了完整的代码示例。最后,针对多线程环境提出了使用两把锁的解决方案,以平衡并发性能与线程安全性。

Read more

Flutter 组件 ignorium 的适配 鸿蒙Harmony 实战 - 驾驭代码生成忽略审计、实现鸿蒙端构建产物精准管理与资源泄露防护方案

Flutter 组件 ignorium 的适配 鸿蒙Harmony 实战 - 驾驭代码生成忽略审计、实现鸿蒙端构建产物精准管理与资源泄露防护方案

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 ignorium 的适配 鸿蒙Harmony 实战 - 驾驭代码生成忽略审计、实现鸿蒙端构建产物精准管理与资源泄露防护方案 前言 在鸿蒙(OpenHarmony)生态的超大规模工程开发中,代码生成(Code Generation)技术(如 build_runner)是提效的利器,但同时也带来了一个令人头疼的并发症:构建产物的急剧膨胀。面对动辄数千个生成的 .g.dart、.fb.dart 以及各种缓存占位文件。如果缺乏一套严密的忽略审计机制,不仅会导致 IDE 索引变慢、IDE 搜索结果被垃圾信息淹没,更严重的是,某些带有敏感信息的生成代码可能会被误提交到仓库中。 我们需要一种“逻辑可控”的构建过滤器。 ignorium 是一套专为代码生成与静态分析设计的忽略路径审计引擎。它允许你通过定义严密的模式规则。精确控制哪些生成文件应该被存留,哪些应该在构建后立即从宿主机环境抹除。

By Ne0inhk

Linux:初始网络(下)

或许你有一个疑问,“发请求、收响应”,却不清楚数据在网线里到底是怎么从一台主机走到另一台主机的。这篇博客在上一篇博客基础上,将最基础的局域网通信原理出发,拆解数据封装与解包的核心逻辑,再延伸到跨网段的网络传输,帮你建立起网络传输的完整宏观认知,所以大家要认真阅读啦~~ 一、同局域网通信:以太网内的主机如何直接对话 局域网是我们最常接触的网络场景,比如家里的路由器连接的电脑、手机,公司内网的办公设备,都属于同一个局域网。我们先从最核心的问题切入,理解局域网通信的底层逻辑 1. 核心问题:同一局域网的两台主机,能直接通信吗? 答案是:完全可以!局域网内的主机通信,本质是基于以太网协议、通过 MAC 地址完成的二层直连通信,原理就像我们在同一个教室里上课:老师喊出同学的名字,全班同学都能听到这个声音,但只有名字对应的同学会做出回应,其他同学会自动忽略这个信息 2. 局域网通信的唯一身份标识:MAC 地址 在以太网的局域网里,每一台主机的唯一性,靠的就是 MAC 地址来保证。 * 核心定义:MAC 地址用来识别数据链路层中相连的节点,是网卡的 “物理身份证”

By Ne0inhk
Flutter for OpenHarmony:leak_tracker 自动监测内存泄漏,精准定位未释放对象(内存性能优化) 深度解析与鸿蒙适配指南

Flutter for OpenHarmony:leak_tracker 自动监测内存泄漏,精准定位未释放对象(内存性能优化) 深度解析与鸿蒙适配指南

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net 前言 内存泄漏(Memory Leak)是移动应用开发中最隐蔽的杀手。在 Flutter 中,虽然 Dart 有垃圾回收(GC)机制,但如果一个对象(如 Widget State、Controller)被全局变量、单例、或者未取消的 StreamSubscription 意外引用,GC 就无法回收它。 这会导致: 1. 内存占用持续飙升,最终 OOM (Out of Memory) 崩溃。 2. UI 卡顿,因为 GC 频繁触发(Stop-the-world)。 3. 后台保活失败,被系统激进查杀。 在

By Ne0inhk
终极指南:全面精通 Docker 在 Ubuntu、CentOS 及 Windows 上的安装与实战配置

终极指南:全面精通 Docker 在 Ubuntu、CentOS 及 Windows 上的安装与实战配置

在当今飞速发展的软件开发与运维(DevOps)领域,Docker 已然成为一项不可或缺的革命性技术。它通过“容器化”这一轻量级的虚拟化方案,将应用程序及其所有依赖项打包到一个可移植的容器中,从而确保了从开发、测试到生产环境的高度一致性与可靠性。无论您是初涉容器世界的开发者,还是寻求标准化部署流程的运维工程师,掌握 Docker 的安装与配置都是您的必修课。 本指南将以前所未有的深度,为您提供一份跨越三大主流操作系统——Ubuntu、CentOS 和 Windows——的 Docker 安装与高级配置的终极手册。我们将不仅仅是罗列命令,而是深入剖析每一步操作背后的原理,解读每一个配置项的意义,并结合源文件中的高清截图,为您带来身临其境的学习体验,确保您在读完本文后,能够充满自信地驾驭 Docker 的安装与维护。 第一章:Ubuntu 环境下的 Docker 之旅——从零到精通 Ubuntu,作为广受欢迎的 Linux 发行版,是运行 Docker 的理想平台。我们将从环境检查开始,一步步完成 Docker

By Ne0inhk