【Linux】POSIX信号量

news2024/10/5 7:22:02

目录

  • 🌈前言
  • 🌸1、POSIX信号量
    • 🍨1.1、概念
    • 🍧1.2、PV操作
  • 🌺2、POSIX信号量相关API
    • 🍨2.1、初始化和销毁信号量
    • 🍧2.2、等待信号量(P)
    • 🍰2.3、发布信号量(V)
  • 🍀3、基于环形队列的生产消费模型
    • 🍨3.1、设计原理
    • 🍨3.1、实现代码

🌈前言

这篇文章给大家带来线程同步与互斥的学习!!!


🌸1、POSIX信号量

🍨1.1、概念

概念⭐⭐⭐⭐⭐
  • POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。 但POSIX可以用于线程间同步
  • 信号量是一个计数器,描述临界资源数量的计数器
  • 只要信号量申请成功了,就一定能获得指定的资源
  • 临界资源可以当作整体,也可以结合场景分成一小块一小块(比如:看电影,整个电影院就是临界资源,里面的座位就是一小块资源
  • 多线程获取一部分资源时,如果访问到相同的资源,是由程序员保证它们不访问到相同的资源的(设置判断)

在这里插入图片描述

资源预定
  • 当我们申请mutex时,只要我们拿到了锁,当前被锁保护的临界资源就是我的了
  • 访问临界资源被切换时,也不用担心,因为锁已经被我申请了,别人申请不了
  • 信号量也是一样,只要申请成功,就能获取指定的资源,切换也不受影响
  • 资源预定机制:申请属于自己的资源,但还没有使用,就叫做资源预定

🍧1.2、PV操作

概念
  • 既然信号是一个计数器,那么计数器就有自增和自减的操作
  • 信号量中的自减操作对应的是P操作,自增操作对应的是V操作
  • 原生的自增自减操作在汇编种不是原子的,但是信号量的PV操作是原子的
  • PV操作对应的是申请信号量资源和归还信号量资源
二元信号量
  • 二元信号量:申请信号量时,设置计数器为1,它只有0和1二种操作
  • 二元信号量PV操作:申请信号量时,1自减变成0,申请成功,归还时又会自减变成0
// 二元信号量其实是一个互斥锁
信号量:1
P -- 1 -- 0 -- 加锁
V -- 0 -- 1 --释放锁
// 二元信号量 == 互斥锁

🌺2、POSIX信号量相关API

信号量的全部接口在编译时,都要加【-lpthread】选项,因为它是第三方库中的头文件

🍨2.1、初始化和销毁信号量

初始化信号量:

#include <semaphore.h>
typedef union
{
  char __size[__SIZEOF_SEM_T];
  long int __align;
} sem_t;

int sem_init(sem_t *sem, int pshared, unsigned int value);
函数解析
  • 作用:sem_init()初始化sem指向的地址处的未命名的信号量
  • sem:取地址信号量(信号量类型是sem_t)
  • pshared:0表示线程间共享,非零表示进程间共享
  • value:用于指定信号量的初始值(可以认为是计数器的初始值)
  • 返回值:成功时,sem_init()返回0;出现错误时,返回-1,并设置errno以指示错误
注意
  • 初始化已初始化的信号量会导致未定义的行为

销毁信号量:

#include <semaphore.h>
typedef union
{
  char __size[__SIZEOF_SEM_T];
  long int __align;
} sem_t;

int sem_destroy(sem_t *sem);
函数解析
  • sem:取地址信号量(信号量类型是sem_t)
  • 返回值:成功时,sem_destroy()返回0;出现错误时,返回-1,并设置errno以指示错误
注意
  • 销毁其他进程或线程当前被阻止的信号量会产生未定义的行为
  • 重新初始化信号量之前,使用已被破坏的信号量会产生未定义的结果

🍧2.2、等待信号量(P)

#include <semaphore.h>
typedef union
{
  char __size[__SIZEOF_SEM_T];
  long int __align;
} sem_t;

int sem_wait(sem_t *sem); //P() -- P操作,信号量减一
函数解析
  • 功能:P操作,等待信号量,会将信号量的值减1
  • sem:取地址信号量(信号量类型是sem_t)
  • 返回值:sem_wait()成功时返回0;出错时,信号量的值保持不变返回-1,并且设置errno以指示错误
  • 注意:如果信号量当前的值为零,那么该调用将阻塞/挂起等待,直到可以执行减量(即:信号量值高于零

🍰2.3、发布信号量(V)

#include <semaphore.h>
typedef union
{
  char __size[__SIZEOF_SEM_T];
  long int __align;
} sem_t;

int sem_post(sem_t *sem);//V() -- V操作,信号量加1
函数解析
  • 功能:V操作,发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1
  • sem:取地址信号量(信号量类型是sem_t)
  • 返回值:成功时,sem_post()返回0;出现错误时,信号量的值保持不变返回-1,并设置errno以指示错误
  • 如果信号量的值因此变为大于零,则在sem_wait()调用中被阻塞的另一个进程或线程将被唤醒,并且继续往下运行

🍀3、基于环形队列的生产消费模型

上一篇文章已经讲了生产消费模型的原理【跳转多线程同步与互斥】

🍨3.1、设计原理

环形队列的设计方法
  • 环形队列可以使用链表和数组来进行设计
  • 队列是连续的,最好使用数组来设计,因为数组是连续存储的,在CPU高速缓存中命中率高,而链表是随机存储的,CPU高速缓存命中率低
  • 环形队列实现方法可以使用计数器、取模运算、预留一个空位置方法等等来进行实现…
  • 环形结构起始状态和结束状态都是一样的,不好判断为空或者为满,可以通过加计数器或者标记位来判断满或者空。另外也可以预留一个空的位置,作为满的状态

在这里插入图片描述


使用信号量设置基于生产消费的环形队列 – 信号量本质是一个计数器

图解:

在这里插入图片描述

总结
  • 生产和消费线程可能会访问同一个位置
  • 只有环形队列为空或为满的时候,才会访问同一个位置,我们让它们同步与互斥(信号量+互斥锁)的走就不会出现这个问题了
  • 其他时候,都是访问不同的位置(可以并发的去执行)
  • 队列为空时:消费者线程不能超过生产者线程,因为队列没有数据 – 生产者线程先走
  • 队列为满时,生产者线程不能超过消费者线程,可能导致数据覆盖 – 消费者线程先走

🍨3.1、实现代码

RingQueue.hpp

#pragma once

#include <iostream>
#include <vector>
#include <string>
#include <semaphore.h>

using namespace std;

const int gCap = 10;

template <class T>
class RingQueue
{
public:
    RingQueue(int cap = gCap): ringqueue_(cap), pIndex_(0), cIndex_(0)
    {
        // 生产
        sem_init(&roomSem_, 0, ringqueue_.size());
        // 消费
        sem_init(&dataSem_, 0, 0);

        pthread_mutex_init(&pmutex_ ,nullptr);
        pthread_mutex_init(&cmutex_ ,nullptr);
    }
    
    // 生产
    void push(const T &in)
    {
    	// P操作
        sem_wait(&roomSem_);
        pthread_mutex_lock(&pmutex_);

        ringqueue_[pIndex_] = in; //生产的过程
        pIndex_++;   // 写入位置后移
        pIndex_ %= ringqueue_.size(); // 更新下标,保证环形特征

        pthread_mutex_unlock(&pmutex_);
        // V操作
        sem_post(&dataSem_);
    }
    
    // 消费
    T pop()
    {
    	// P操作
        sem_wait(&dataSem_);
        pthread_mutex_lock(&cmutex_);

        T temp = ringqueue_[cIndex_];
        cIndex_++;
        cIndex_ %= ringqueue_.size();// 更新下标,保证环形特征

        pthread_mutex_unlock(&cmutex_);
        // V操作
        sem_post(&roomSem_);

        return temp;
    }
    ~RingQueue()
    {
        sem_destroy(&roomSem_);
        sem_destroy(&dataSem_);

        pthread_mutex_destroy(&pmutex_);
        pthread_mutex_destroy(&cmutex_);
    }
private:
    vector<T> ringqueue_; // 环形队列
    sem_t roomSem_;       // 衡量空间计数器,productor
    sem_t dataSem_;       // 衡量数据计数器,consumer
    uint32_t pIndex_;     // 当前生产者写入的位置, 如果是多线程,pIndex_也是临界资源
    uint32_t cIndex_;     // 当前消费者读取的位置,如果是多线程,cIndex_也是临界资源

	// 为什么要加锁呢? 因为多线程的情况下,需要保护下标自增操作,它们不是原子的
    pthread_mutex_t pmutex_; // 生产者互斥锁
    pthread_mutex_t cmutex_; // 消费者互斥锁
};

test.cpp – 测试代码

#include "RingQueue.hpp"
#include <ctime>
#include <unistd.h>

// 不要只关心把数据或者任务,从ringqueue 放拿的过程,获取数据或者任务,处理数据或者任务,也是需要花时间的!

void *productor(void *args)
{
    RingQueue<int> *rqp = static_cast<RingQueue<int> *>(args);
    while(true)
    {
        int data = rand()%10;
        rqp->push(data);
        cout << "pthread[" << pthread_self() << "]" << " 生产了一个数据: " << data << endl;
        sleep(1);
    }
}

void *consumer(void *args)
{
    RingQueue<int> *rqp = static_cast<RingQueue<int> *>(args);
    while(true)
    {
        //sleep(10);
        int data = rqp->pop();
        cout << "pthread[" << pthread_self() << "]" << " 消费了一个数据: " << data << endl;
    }
}

int main()
{
    srand((unsigned long)time(nullptr)^getpid());

    RingQueue<int> rq;

    pthread_t c1,c2,c3, p1,p2,p3;
    pthread_create(&p1, nullptr, productor, &rq);
    pthread_create(&p2, nullptr, productor, &rq);
    pthread_create(&p3, nullptr, productor, &rq);
    pthread_create(&c1, nullptr, consumer, &rq);
    pthread_create(&c2, nullptr, consumer, &rq);
    pthread_create(&c3, nullptr, consumer, &rq);


    pthread_join(c1, nullptr);
    pthread_join(c2, nullptr);
    pthread_join(c3, nullptr);
    pthread_join(p1, nullptr);
    pthread_join(p2, nullptr);
    pthread_join(p3, nullptr);

    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/180045.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ImmutableList hessian2序列化失败问题分析

问题描述 A服务提供了个RPC接口给B服务使用&#xff0c;入参里有个参数是List类型&#xff0c;B服务在传参时使用Guava里的 ImmutableList&#xff0c;结果发生报错。 其中&#xff0c;B服务即consumer端的异常为&#xff1a;「com.alibaba.dubbo.remoting.RemotingException:…

AD采集之离散化概念(Quantizer模型使用介绍)

模拟量采集的PLC程序和功能块算法,可以参看下面的文章链接: PLC模拟量采集算法数学基础(线性传感器)_RXXW_Dor的博客-CSDN博客_模拟量采集线性校准模拟量采集库如何设计,具体算法代码请参看我的另一篇博文:PLC模拟量输入 模拟量转换FC:S_ITR_RXXW_BOSS的博客-CSDN博客_…

缓存数据库memcached

目录 一.memcached简介 memcached简介 memcached的特点 二.memcached安装 2.1.yum安装 2.2.源码安装 三.memcached命令 3.1.memcached的特征 3.2.memcached的set命令 3.3.memcached的get命令 四.memcached应用实例配置 4.1.图示 4.2.基础配置 4.3.环境规划 4.3.1…

SpringBoot程序的打包和运行

程序打包 SpringBoot程序是基于Maven创建的&#xff0c;在Maven中提供有打包的指令&#xff0c;叫做package。本操作可以在Idea环境下执行。 mvn package​ 打包后会产生一个与工程名类似的jar文件&#xff0c;其名称是由模块名版本号.jar组成的。 程序运行 ​ 程序包打好以…

TeeGrid for .NET 2023

TeeGrid for .NET 2023 TeeGrid for.NET为Visual Studio项目提供了一个非常快速的数据网格。打包功能的.NET数据网格提供了诸如排序、分组或过滤网格数据、可调整列大小、主详细视图、可拖动选择、网格滚动等功能。只需使用几个属性设置即可启用这些内置功能。TeeGrid可以链接到…

【一文看懂 Redis 核心】 基础数据结构 架构设计 存储 集群

基础数据结构 & 架构设计 & 存储 & 集群 redis 简单来说其实就是一个基于内存的 key - value 数据库&#xff0c;它本身结构的前提就是 key - value 类似于 Java 中的 HashMap &#xff0c;所以我们在聊 redis 的时候始终要记得这个前提&#xff0c;同时 redis 在…

【SpringCloud】Feign 和 OpenFeign 两者的异同点

Feign 和 OpenFeign 两者共同点Feign和OpenFeign作用一样&#xff0c;都是进行远程调用的组件。里面都内置了 Ribbon。都是加在消费端的注解&#xff0c;让消费端可以调用其他生产者的服务。Feign 和 OpenFeign 两者区别&#xff08;1&#xff09;依赖不同Feign 的依赖<!-- …

十九、Gtk4-Ui file for menu and action entries

Ui file for menu 你可能认为构建菜单真的很麻烦。是的&#xff0c;程序很复杂&#xff0c;需要很多时间来编码。这种情况类似于构建小构建。当我们构建部件时&#xff0c;使用ui文件是避免这种复杂性的好方法。菜单也是如此。 菜单的ui文件有界面和菜单标签。文件以interfac…

JavaScript 基本认识

JavaScript 简介 JavaScript 是什么&#xff1f; JavaScript 是互联网最流行的脚本语言&#xff0c;这门语言可用于 HTML 和 Web&#xff0c;更可广泛用于服务器、PC、笔记本电脑、平板电脑和智能手机等设备。 JavaScript 是脚本语言&#xff1f; HTML 是超文本标记语言&am…

Groovy实现热部署

Groovy实现热部署一、概述二、准备工作2.1 规则接口IRule三、非Spring环境Groovy文件方式3.1 Groovy文件3.2 读取并生成实例3.3 使用这个实现四、数据库Groovy脚本方式4.1 Groovy脚本4.2 读取并生成实例五、Spring中使用Groovy的方式5.1 Groovy文件5.2 读取并生成实例5.3 使用这…

css sprite雪碧图制作,使用以及相关,图文gif

写在前面&#xff1a; 在网页制作中&#xff0c;雪碧图也是前端攻城狮必须掌握的一项小技能。百度词条对雪碧图的解释是&#xff1a;CSS雪碧 即CSS Sprite&#xff0c;也有人叫它CSS精灵&#xff0c;是一种CSS图像合并技术&#xff0c;该方法是将小图标和背景图像合并到一张图…

计算机组成原理 | 第一章:概论 | 冯诺依曼计算机 | 计算机硬件

文章目录&#x1f4da;冯诺依曼计算机的特点&#x1f4da;计算机硬件组成框图&#x1f4da;计算机硬件的主要技术指标&#x1f407;非时间指标&#x1f407;时间指标&#x1f511;计算技巧归纳&#x1f4da;小结&#x1f511;本章掌握要点&#x1f407;补充思考题&#x1f4da;…

[电商实时数仓] 用户行为数据和业务数据采集以及ODS层

文章目录1.数据仓库环境准备1.1 导入依赖1.2 创建相关包2.数据仓库运行环境2.1 Hbase环境2.2 模拟数据3.数仓开发之ODS层3.1 mysql配置修改3.2 FlinkCDC的程序3.3 结果检测1.数据仓库环境准备 1.1 导入依赖 <properties><java.version>1.8</java.version>&l…

为什么你的Facebook广告策略应该包括SEO

最近在看了很多关于 SEO的文章&#xff0c;今天想跟大家分享一些我个人关于 Facebook广告中的 SEO策略&#xff0c;以及它为什么是必要的。虽然在我看来&#xff0c;所有营销手段都需要结合 SEO才能发挥最大作用&#xff0c;但这并不意味着要完全放弃 SEO。如果你对以下问题感兴…

分享147个ASP源码,总有一款适合您

ASP源码 分享147个ASP源码&#xff0c;总有一款适合您 下面是文件的名字&#xff0c;我放了一些图片&#xff0c;文章里不是所有的图主要是放不下...&#xff0c; 147个ASP源码下载链接&#xff1a;https://pan.baidu.com/s/1us1KTsxeaZlosHsqvrkC5Q?pwd81pl 提取码&#x…

Leetcode:51. N 皇后(C++)

目录 问题描述&#xff1a; 实现代码与解析&#xff1a; 回溯&#xff1a; 原理思路&#xff1a; 问题描述&#xff1a; 按照国际象棋的规则&#xff0c;皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。 n 皇后问题 研究的是如何将 n 个皇后放置在 nn 的棋盘上&a…

数字电位器程控可调电阻ic

一、前言 数字电位器又叫可编程电阻器&#xff0c;是一种替代传统机械电位器的新型CMOS数字、模拟混合信号处理集成电路&#xff0c;不需要搭建复杂的电路环境即可简单的通过CPU数字通讯实现电路调节&#xff0c;数字电位器也不能完全替代传统的机械电位器&#xff0c;在很多场…

Sentinel(限流、熔断、降级)、SpringBoot整合Sentinel、Sentinel的使用-60

一&#xff1a;Sentinel简介 Sentinel就是分布式系统的流量防卫兵 随着微服务的流行&#xff0c;服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点&#xff0c;从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。 1.1 官方文档 官方文档&#…

基于OpenXR,Collabora推开源VI-SLAM AR/VR定位系统

XR最关键的难题之一就是定位&#xff0c;为了定位XR头显在现实世界中的位置和角度&#xff0c;厂商们采用了多种方案&#xff0c;比如机械传感器、惯性传感器、磁传感器、声学传感器等等。这些定位方式有一个共同的问题&#xff0c;那就是传感器不够完善&#xff0c;且会产生噪…

uniapp的父传子,子传父,子组件与父组件数据同步(.sync)的理解

父传子&#xff1a; 父调用 绑定的子组件中state然后 mystate1赋值false 给子组件中的state。并在子组件中显示父中传来的值。 注意要在子组件中设置 props【属性】不然父中的值无法传过去。 <view >开启{{mystate1}}</view> --调用子组件mypop&#xff0c;并传值…