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

【Linux】POSIX信号量

📝前言:

这篇文章我们来讲讲Linux——POSIX信号量

  1. 回顾信号量
  2. POSIX信号量接口
  3. 基于环形队列的生产者消费者模型

🎬个人简介:努力学习ing
📋个人专栏:Linux
🎀CSDN主页 愚润求学
🌄其他专栏:C++学习笔记,C语言入门基础,python入门基础,C++刷题专栏


目录

  • 一、回顾信号量
  • 二、POSIX信号量接口
    • 1. 初始化和销毁
    • 2. 等待和发布
  • 三、基于环形队列的生产者消费者模型
    • 1. 基本实现
      • 1.1 思路
      • 1.2 实现及运行
        • 实现
        • 运行
    • 2. 细节解刨
      • 2.1 多多模型的加锁
      • 2.2 对比互斥锁 + 条件变量实现的模型

一、回顾信号量

之前,我们讲述过System V版本的信号量(用于进程)
POSIX版本的信号量(可用于线程),如果要用于进程可以强转,比如把共享资源的前面一部分内容直接强转成POSIX版本的信号量(相当于顶替定义操作了)

  • 信号是一个计数器,用来描述一大片共享资源中可用资源的数量(我们把这一大片共享资源给划分了)
  • 信号量的本质是对资源的预订机制
    • 线程要获得资源,要先 P 操作 (等待操作,计数器--,申请信号量)
    • 使用完资源以后,要 V 操作 (发布操作,计数器++,释放信号量)
  • P 操作和 V 操作本身是原子的
  • 如果当前信号量不够了(P的时候,信号量为0),则该线程会被加入到信号量的等待队列中。(直到有线程 V 操作把信号量给放出来了)

二、POSIX信号量接口

1. 初始化和销毁

初始化

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
  • sem_t信号量的类型
  • sem:要初始化的信号量
  • pshared:零表示线程间共享,非零表示进程间共享(传0就行)
  • value:信号量初始值
  • 返回值:成功,零,失败,非零

销毁

int sem_destroy(sem_t *sem);

2. 等待和发布

等待

int sem_wait(sem_t *sem);  // P 操作
  • 当信号量为0,线程就会被放到信号量等待队列里面等
  • 这个操作是原子的
  • 如果为负数就不能访问临界资源,也就是说,在访问临界资源之前,信号量已经对“条件”进行了判断(相当于互斥锁的while

发布

int sem_post(sem_t *sem); // V 操作

三、基于环形队列的生产者消费者模型

在这里插入图片描述

1. 基本实现

1.1 思路

问题描述

  • 生产者往空的位置上生产,消费者消费不为空的位置。
  • 队列为空:生产者先生产(消费者不能超过生产者)
  • 队列为满:消费者先消费(生产者不能套消费者一圈以上)

问题抽象

  • 生产者只关注空盘子
  • 消费者只关注非空盘子
  • 两者在同一位置时(为空 / 为满),两者才需要同步和互斥
  • 不在同一位置时,生产者和消费者之间行为独立

模型特点

  • 3 个关系(基本不变)
    • 生产与生产:互斥(单生产单消费时退化,只有一个生产者就没有互斥)
    • 消费与消费:互斥
    • 生产与消费
      • 不在同一位置时:互不影响(无关系)
      • 在同一个位置时:互斥 + 同步
  • 2 个角色:生产者和消费者线程
  • 1 个交易场所(环形队列)

实现

  • 用二元信号量(且这两个信号量之间有关联)
    • 一个描述空盘子数量
    • 一个描述非空盘子的数量
  • vector来模拟环形队列,生产和消费往vector对应下标里进行

1.2 实现及运行

实现

代码实现

RingQueue.hpp文件:

#include <semaphore.h>
#include <vector>
#include <string>
#include <pthread.h>using namespace std;template <typename T>
class RingQueue
{
public:RingQueue(int size):_cap(size),_p_step(0),_c_step(0){_ringqueue.resize(size);sem_init(&_empty_sem, 0, size);sem_init(&_noempty_sem, 0, 0);  // 初始时,没有非空位置pthread_mutex_init(&_mutex_p, nullptr);pthread_mutex_init(&_mutex_c, nullptr);}~RingQueue() {sem_destroy(&_empty_sem);sem_destroy(&_noempty_sem);pthread_mutex_destroy(&_mutex_p);pthread_mutex_destroy(&_mutex_c);}void Push(const T &data) {// 1. 申请信号量sem_wait(&_empty_sem); // 如果失败就阻塞// 多生产之间 要加锁pthread_mutex_lock(&_mutex_p);// 2. 生产_ringqueue[_p_step] = data;// 3. 生产者位置改变(并维护环形队列)_p_step = (_p_step + 1) % _cap;// 4. 改变 _noempty_sem,通知消费者sem_post(&_noempty_sem);// 解锁pthread_mutex_unlock(&_mutex_p);}void Pop(T* data) {// 1. 申请信号量sem_wait(&_noempty_sem); // 如果失败就阻塞// 多消费者之间 要加锁pthread_mutex_lock(&_mutex_c);// 2. 消费*data = _ringqueue[_c_step];// 3. 消费者位置改变(并维护环形队列)_c_step = (_c_step + 1) % _cap;// 4. 改变 _noempty_sem,通知生产者sem_post(&_empty_sem);// 解锁pthread_mutex_unlock(&_mutex_c);}private:vector<T> _ringqueue;int _cap;sem_t _empty_sem; // 描述空位置sem_t _noempty_sem;int _p_step; // 生产者所在位置int _c_step;// 多生产,多消费需要加锁,维护生产者和生产者,消费者和消费者之间的互斥关系pthread_mutex_t _mutex_p;pthread_mutex_t _mutex_c;
};

Main.cpp文件

#include "RingQueue.hpp"
#include <iostream>
#include <unistd.h>int num = 1;void* Pro(void* args)
{RingQueue<int>* p = static_cast<RingQueue<int>*>(args);while(true){// sleep(2);p->Push(num);cout << "生产了一个任务: " << num <<endl;num++;}
}void* Com(void* args)
{RingQueue<int>* p = static_cast<RingQueue<int>*>(args);while(true){sleep(2);int ret;p->Pop(&ret);cout << "消费了一个任务: " << ret << endl;}
}int main()
{int num = 0;RingQueue<int> super(5);// 单生产,单消费// pthread_t p[1], c[1];// pthread_create(&p[0], nullptr, Pro, &super);// pthread_create(&c[0], nullptr, Com, &super);// pthread_join(p[0], nullptr);// pthread_join(c[0], nullptr);// 多多pthread_t p[3], c[2];pthread_create(&p[0], nullptr, Pro, &super);pthread_create(&p[1], nullptr, Pro, &super);pthread_create(&p[2], nullptr, Pro, &super);pthread_create(&c[0], nullptr, Com, &super);pthread_create(&c[1], nullptr, Com, &super);pthread_join(p[0], nullptr);pthread_join(p[1], nullptr);pthread_join(p[2], nullptr);pthread_join(c[0], nullptr);pthread_join(c[1], nullptr);
}
运行

运行结果:
单生产单消费,且消费更快,则:生产一个消费一个
在这里插入图片描述
cout输出到屏幕上也有并发问题。

多生产,多消费(生产慢,消费快):
在这里插入图片描述
对全局变量num++操作不是原子的,也会有并发问题。
但是我们基本可以看出来消费的都是旧任务,生产的是新任务。

2. 细节解刨

2.1 多多模型的加锁

多多模型相比单单模型,缺少的就是3个关系中的前两个
多多模型,加锁的位置

  • 在申请信号量之前和之后加都可以,但是在之后加效率更高
  • 因为申请信号量也是原子操作,多个线程可以先分配好信号量,然后再去竞争锁。
  • 如果先申请锁的话,那么每次只能把信号量分给一个线程。

2.2 对比互斥锁 + 条件变量实现的模型

互斥锁 + 条件变量

  • 互斥锁 + 条件变量的实现,用在当资源作为整体使用的时候
    • 我们使用queue作为交易场所,但是queue不能同时往多个地方入队,所以只能整体使用

信号量

  • 而信号量可以用于:当资源整体能被划分成多个小资源,且小资源可以同时访问的场景
    • 如果资源数量size1,则这时候也就退化成了互斥锁 + 条件变量模型

进一步封装

  • 当然我们也可以把库的信号量 / 锁封装起来,让代码更优雅
  • 然后把访问临界区的代码用{}扩起来,在{}内定义的变量声明周期随{}

🌈我的分享也就到此结束啦🌈
要是我的分享也能对你的学习起到帮助,那简直是太酷啦!
若有不足,还请大家多多指正,我们一起学习交流!
📢公主,王子:点赞👍→收藏⭐→关注🔍
感谢大家的观看和支持!祝大家都能得偿所愿,天天开心!!!

http://www.lqws.cn/news/144541.html

相关文章:

  • 第20讲、Odoo 18 翻译机制与 PO 文件详解
  • YOLOv8 × VisDrone 全流程实战:训练你的无人机识别模型 AI(第一部分:数据集准备)
  • 鸿蒙缺少WMIC missing WMIC
  • 《C++ 模板》
  • 仓库自动化搬运:自动叉车与AGV选型要点及核心技术解析
  • MyBatis之测试添加功能
  • 18650锂电池组点焊机:高效组装锂电池的关键工具|比斯特自动化
  • XDMA pcie环路测试
  • Oracle 的 SEC_CASE_SENSITIVE_LOGON 参数
  • 代码中文抽取工具并替换工具(以ts为例)
  • 在 CentOS 上将 Ansible 项目推送到 GitHub 的完整指南
  • 高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数
  • [论文阅读] 人工智能+软件工程 | MemFL:给大模型装上“项目记忆”,让软件故障定位又快又准
  • [pdf、epub]300道《软件方法》强化自测题业务建模需求分析共257页(202505更新)
  • 机器学习监督学习实战四:九种回归算法对波士顿房价数据进行回归预测和评估方法可视化
  • 【从0-1的CSS】第1篇:CSS简介,选择器已经常用样式
  • centos部署k8s v1.33版本
  • asp.net mvc如何简化控制器逻辑
  • vue2 , el-select 多选树结构,可重名
  • 使用vite-plugin-html在 HTML 文件中动态注入数据,如元数据、环境变量、标题
  • Go中的协程并发和并发panic处理
  • MVCC机制:Undo Log版本链与ReadView机制
  • MVCC实现原理
  • bug 记录 - 使用 el-dialog 的 before-close 的坑
  • 网站首页菜单两种布局vue+elementui顶部和左侧栏导航
  • Pandas和Django的示例Demo
  • wpf Behaviors库实现支持多选操作进行后台绑定数据的ListView
  • MySQL 高可用基石 - 复制监控与常见 HA 方案
  • DBSyncer:开源数据库同步利器,MySQL/Oracle/ES/SqlServer/PG/
  • 企业培训学习考试系统源码 ThinkPHP框架+Uniapp支持多终端适配部署