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

【Linux庖丁解牛】— 信号量 !

1. POSIX信号量

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

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

多线程使用资源其实有两种场景:

> 将目标资源整体使用。【比如我们基于阻塞队列设计的生产者消费者模型就是将整个阻塞队列上锁,也就是将目标资源整体使用。这可以用互斥锁实现】

> 将目标资源分块使用。【可以用信号量办到】

使用信号量来维护资源的安全要考虑两点:

1. 不要让多个线程访问同一块资源。

2. 不要放入过多的线程进来

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

在环形队列中我们先做如下约定:

1. 队列为空,生产者先运行。

2. 队列为满,消费者先运行。

3. 生产者不能将消费者套一个圈以上。

4. 消费者不能超过生产者。

基于以上的约定,我们可以得出以下结论

1. 只要在环形队列中,我们不访问同一个位置,我们就可以同时运行。

2. 只有队列中为空或者为满的时候,生产者和消费者才可能在同一个位置。

3. 如果在同一位置就只能互斥,为空则生产者先运行。为满,则消费者先运行。

但是,我们怎么保证以上的4个约定呢??

答案就是用信号量来保证!!下面是使用信号量保证约定的伪代码流程:

对于生产者来说,资源就是每一个空位置,我们用sem_blank信号量来表示。而对于消费者来说,资源就是数据,我们用sem_data来表示。PV操作本质上就是在申请信号量,这是原子性的!当位置为0时,生产者就会在P操作上阻塞,直到位置不为0【2. 队列为满,消费者先运行3. 生产者不能将消费者套一个圈以上】。同理,当数据为0时,消费者就会在P操作上阻塞,直到数据不为0【1. 队列为空,生产者先运行4. 消费者不能超过生产者】!当队列不为空,也不为满时,它们就可以同时操作

3. 信号量接口 

认识接口:

1. 初始化信号量:pshared【0表示线程共享,非零表示线程间共享】,value为信号量的初始值。

2.  销毁信号量

3. 信号量P操作

4. 信号量V操作 

 封装信号量:

sem.hpp

#pragma once#include <iostream>
#include <pthread.h>
#include <semaphore.h>namespace sem_module
{const unsigned int default_val = 5;class sem{public:sem(unsigned int val = default_val){sem_init(&_sem, 0, val);}// P操作void P(){sem_wait(&_sem);}// V操作void v(){sem_post(&_sem);}~sem(){sem_destroy(&_sem);}private:sem_t _sem;};}

4. 模型demon代码

ring_queue.hpp

#pragma once#include "sem.hpp"
#include "mutex.hpp"
#include <iostream>
#include <vector>using namespace sem_module;
using namespace mutex_module;static const int default_cap = 5; // for testtemplate <typename T>
class ring_queue
{
public:ring_queue(int cap = default_cap): _rq(cap),_cap(cap),_data_sem(0),_blank_sem(cap),_p_step(0),_c_step(0){}// 生产者void product(const T &in){// 申请位置信号量_blank_sem.P();// 先申请信号量,再加锁// 信号量的本质是对资源的预定机制,让每个消费者线程先预定自己的资源再来竞争锁拿到自己的资源{lock_guard lg(&_pmutex);// 生产_rq[_p_step] = in;// 更新下标_p_step++;_p_step %= _cap;}// 更新数据信号量_data_sem.v();}// 消费者void consum(T *out){// 申请数据信号量_data_sem.P();{lock_guard lg(&mutex);// 消费*out = _rq[_c_step];// 更新下标_c_step++;_c_step %= _cap;}// 更新位置信号量_blank_sem.v();}~ring_queue() {}private:std::vector<T> _rq;int _cap;       // 循环队列容量sem _data_sem;  // 数据信号量sem _blank_sem; // 位置信号量int _p_step;    // 生产者下标int _c_step;    // 消费者下标// 维护多线程之间的互斥关系mutex _pmutex;mutex _cmutex;
};

main.cc

​
#include "ring_queue.hpp"
#include <unistd.h>// 生产者
void *product(void *args)
{int cnt = 1;ring_queue<int> *rq = static_cast<ring_queue<int> *>(args);while (true){// sleep(1);rq->product(cnt);std::cout << "生产了:" << cnt << std::endl;cnt++;}return nullptr;
}// 消费者
void *consum(void *args)
{int t;ring_queue<int> *rq = static_cast<ring_queue<int> *>(args);while (true){sleep(1);rq->consum(&t);std::cout << "消费了:" << t << std::endl;}return nullptr;
}int main()
{// 申请阻塞队列ring_queue<int> *rq = new ring_queue<int>();// 构建生产者和消费者pthread_t p, c;pthread_create(&p, nullptr, product, rq);pthread_create(&p, nullptr, consum, rq);pthread_join(p, nullptr);pthread_join(c, nullptr);return 0;
}​
http://www.lryc.cn/news/597795.html

相关文章:

  • Petalinux的常用指令
  • python3写一个异步流式 http 接口服务调用大模型(async, stream, sanic)---6.2
  • 若依前后端分离版学习笔记(二)——系统菜单介绍
  • 前端资源缓存优化案例:深入探讨 Nginx 配置中的 Cache-Control 头部叠加问题
  • 【科研绘图系列】R语言绘制黑白填充等显著性标记条形图
  • Java按模板导出Excel
  • Redis能完全保证数据不丢失吗?
  • 《WebGL与Three.js打造会“讲故事“的虚拟博物馆》
  • 氢气传感器在氢燃料电池中的应用与技术保障
  • 《狼道》:生存智慧与处世哲学
  • python 字符串常用处理函数
  • 判断矩形能否放入多边形内——cad c# 二次开发实现
  • docker的镜像与推送
  • 卡尔曼滤波数据融合
  • GaussDB null的用法
  • mac测试ollama llamaindex
  • c++--面向对象封装--实践
  • 【2025/07/23】GitHub 今日热门项目
  • git的使用,推送仓库github
  • 【数据结构】——时间与空间复杂度深度解析
  • 第一章:Go语言基础入门之Hello World与Go程序结构
  • 设置低秩适配器(LoRA)
  • 苍穹外卖DAY11
  • 【网安-小迪】Day5:反弹SHELL不回显带外正反向连接防火墙出入站文件下载
  • android studio打包vue
  • vue写的app设置角标
  • 智能小e-集成配置
  • vue3与ue5通信-工具类
  • 2025年电赛--电源题赛前押题
  • 【每日算法】专题十八_BFS 解决拓扑排序