多线程~POSIX信号量实现生产者消费者模型,PV操作

news2025/1/19 14:12:59

目录

1.信号量的概念

2.sem_t信号量的操作函数

(1).原理

(2).sem_t函数的使用

(3).基于信号量和环形队列的生产者消费者模型

1).大致实现思路

Task.hpp

circular_queue.hpp

circular_cp.cc

运行结果


1.信号量的概念

        信号量本质就是一把计数器,描述临界资源中资源数目的大小,合理使用可以达到对临界资源进行预订的目的。(最多能有多少资源分配给线程)

        多线程预定资源的方法:临界资源如果可以被划分成为一个一个的小资源,如果处理得当,我们也有可能让多个线程同时访问临界资源的不同区域,从而实现并发并行的访问临界资源。

2.sem_t信号量的操作函数

(1).原理

        比如信号量s,我们对信号量进行+1,或者-1的操作时,在底层实际上是用互斥锁保证了线程安全。当对信号量进行-1操作时,先加锁,判断当前信号量是否 <=0,如果是,则挂起当前线程,然后解锁,等待被唤醒,尝试重新去竞争锁;如果当前信号量 >0,则对信号量进行-1操作,然后解锁。这实际上对应的就是PV操作里面的P操作。

        如果要进行+1操作,底层也是进行了加锁和解锁的,对应的是PV操作中的V操作。

sem_wait和sem_post保证了对信号量的申请是原子的,见下方

(2).sem_t函数的使用

  • 定义信号量

sem_t sem

  • 初始化信号量

#include <semaphore.h>

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

参数:

        pshared:0表示线程间共享,非零表示进程间共享

        value:信号量初始值

成功返回0,-1表示失败,错误码(默认是-1)可以设置。

  • 销毁信号量

int sem_destroy(sem_t *sem);

  • 等待信号量

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

int sem_wait(sem_t *sem); //P()

  • 发布信号量

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

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

(3).基于信号量和环形队列的生产者消费者模型

1).大致实现思路

环形队列采用数组模拟,用模运算来模拟环状特性

        环形结构起始状态和结束状态都是一样的,不好判断为空或者为满,所以可以通过加计数器或者标记位来判断满或者空(我们采用的方式,用两个信号量就可以判断队列是空还是满)。另外也可以预留一个空的位置,作为满的状态

        当队列为空的时候,应该只让生产者执行;队列为满的时候,应该只让消费者执行。队列为空或者队列为满的时候,生产者和消费者指向的是同一个位置。

        那么,当队列不为空,不为满的时候,生产者和消费者一定指向的不是同一个位置,此时生产和消费可以并发执行

对于生产者:

  1. 对black资源进行P操作,表示生产者用掉一个black资源
  2. 生产完以后,将数据data放到环形队列中
  3. 对data资源进行V操作,表示生产者生产出一个data资源

对于消费者

  1. 对data资源进行P操作,表示消费者用掉一个data资源
  2. 生产完以后,将数据data从环形队列中删除,空出一个black资源
  3. 对black资源进行V操作,表示消费者消费掉一个data,空出一个black资源

Task.hpp

        消费者要消费的数据,就是要执行的任务,这里使用的Task和我上一篇文章中的Task一样,对两个操作数执行加减乘除运算

#pragma once

#include <iostream>
#include <pthread.h>

namespace ns_task
{
    class Task
    {
    private:
        int _x;
        int _y;
        char _op; // 操作符
    public:
        Task() {}
        Task(int x, int y, char op) : _x(x), _y(y), _op(op)
        {
        }
        std::string Show()
        {
            std::string message = std::to_string(_x);
            message += _op;
            message += std::to_string(_y);
            message += "=?";
            return message;
        }
        int run()
        {
            int res = 0;
            switch (_op)
            {
            case '+':
                res = _x + _y;
                break;
            case '-':
                res = _x - _y;
                break;
            case '*':
                res = _x * _y;
                break;
            case '/':
                res = _x / _y;
                break;
            case '%':
                res = _x % _y;
                break;
            default:
                std::cout << "something wrong" << std::endl;
                break;
            }
            std::cout << "当前任务正在被线程: " << pthread_self() << " 处理: "
                      << _x << _op << _y << "=" << res << std::endl;
            return res;
        }
        ~Task() {}
    };
}

circular_queue.hpp

        使用POSIX库中的信号量实现环形队列,定义两个_c_pos和_p_pos分别表示生产者要生产资源的位置和消费者要消费资源的位置,分别对应的是当前环形队列中,数据的头部+1的位置和尾部的位置。此时环形结构起始状态和结束状态都是一样的,不好判断为空或者为满,定义的两个信号量就很好的解决了这个问题,对于消费者,如果有data资源,就可以进行消费;对于生产者,如果有blank空白资源,就可以进行生产,两者通过信号量实现同步。

#pragma once

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

namespace ns_circular_queue
{
    const int default_len = 10;

    template <class T>
    class CircularQueue
    {
    private:
        std::vector<T> _circular_queue;
        int _len;
        // 生产者关心空位置资源
        sem_t _blank_sem;
        // 消费者关心数据资源
        sem_t _data_sem;

        int _c_pos;
        int _p_pos;

        pthread_mutex_t _c_mtx;
        pthread_mutex_t _p_mtx;

    public:
        CircularQueue(int len = default_len) : _circular_queue(len), _len(len)
        {
            sem_init(&_blank_sem, 0, len);
            sem_init(&_data_sem, 0, 0);
            _c_pos = _p_pos = 0;

            pthread_mutex_init(&_c_mtx, nullptr);
            pthread_mutex_init(&_p_mtx, nullptr);
        }
        ~CircularQueue()
        {
            sem_destroy(&_blank_sem);
            sem_destroy(&_data_sem);

            pthread_mutex_destroy(&_c_mtx);
            pthread_mutex_destroy(&_p_mtx);
        }

    public:
        void push(const T &in)
        {
            // 生产接口
            sem_wait(&_blank_sem); // P(blank),尝试申请blank资源,如果没有blank资源(信号值 <= 0),当前线程会被挂起

            pthread_mutex_lock(&_p_mtx);

            _circular_queue[_p_pos] = in; // 对于生产者,直接在_p_pos处放入生产的数据即可,当前_p_pos处是没有data的

            // 它也变成了临界资源
            _p_pos++;
            _p_pos %= _len;
            pthread_mutex_unlock(&_p_mtx);

            sem_post(&_data_sem); // V(data),释放data资源,如果信号值>0,其他正在调用sem_wait等待信号量的线程将被唤醒
        }
        void pop(T *out)
        {
            // 消费接口
            sem_wait(&_data_sem); // P(data)

            pthread_mutex_lock(&_c_mtx);
            *out = _circular_queue[_c_pos];
            _c_pos++;
            _c_pos %= _len;
            pthread_mutex_unlock(&_c_mtx);

            sem_post(&_blank_sem); // V(blank)
        }
    };
}

circular_cp.cc

        创建3个消费者线程,2个生产者线程,分别消费任务和生产任务。消费任务就是把数据从队列中拿出来,然后处理;生产任务就是把数据放到队列中

#include "circular_queue.hpp"
#include <pthread.h>
#include <time.h>
#include <unistd.h>
#include "Task.hpp"

using namespace ns_circular_queue;
using namespace ns_task;

void *consumer(void *args)
{
    CircularQueue<Task> *cq = (CircularQueue<Task> *)args;
    while (true)
    {
        Task t;
        cq->pop(&t);
        std::cout << "消费数据: " << t.Show()<< "我是线程: " << pthread_self() << std::endl;
        t.run();
        sleep(1);  //1s
    }
}

void *producter(void *args)
{
    CircularQueue<Task> *cq = (CircularQueue<Task> *)args;
    const std::string ops = "+-*/%";
    while (true)
    {
        int x = rand() % 20 + 1;
        int y = rand() % 10 + 1;
        char op = ops[rand() % ops.size()];
        Task t(x, y, op);

        std::cout << "生产数据:  " << t.Show() << "我是线程: " << pthread_self() << std::endl;
        cq->push(t);
        usleep(100000);  //0.1s
    }
}

int main()
{
    srand((long long)time(nullptr));
    CircularQueue<Task> *rq = new CircularQueue<Task>();

    pthread_t c1, c2, c3, p1, p2;

    pthread_create(&c1, nullptr, consumer, (void *)rq);
    pthread_create(&c2, nullptr, consumer, (void *)rq);
    pthread_create(&c3, nullptr, consumer, (void *)rq);
    pthread_create(&p1, nullptr, producter, (void *)rq);
    pthread_create(&p2, nullptr, producter, (void *)rq);

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

    return 0;
}

运行结果

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

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

相关文章

基于java SSH框架的简单医疗管理系统源码+数据库,医疗管理系统基于springmvc+spring+hibernate

医疗管理系统 基于java SSH框架的简单医疗管理系统 环境说明 1、语言及开发环境&#xff1a; 语言实现说明JAVA后端用springmvcspringhibernate&#xff0c;前端使用htmlajax开发环境使用eclipse&#xff0c;maven管理。 数据库使用mysql&#xff1b; 完整代码下载地址&…

3D设计软件SolidWorks特征研究—— 3种放样方式 | 附视频教程

SolidWorks 是世界上第一个基于Windows开发的三维CAD系统&#xff0c;是可实现设计、模拟、成本估算、可制造性检查、CAM、可持续设计和数据管理等多种功能的三维设计软件&#xff0c;包含适用于钣金、焊件、曲面、模具、产品配置、DFM和CAM的专业工具&#xff0c;同时支持ECAD…

跑步耳机入耳式好还是半入耳式好、跑步用的耳机推荐

运动耳机一定是要跟佩戴舒适性、音质、性能关联在一起的&#xff0c;尤其是专业的运动耳机&#xff0c;还要具有久戴舒适运动时还不掉的特点&#xff0c;这个是我认为无论任何价价位的运动耳机都必须首要具备的条件&#xff0c;戴久了不舒服或者总掉&#xff0c;音质再好估计都…

带你了解防火墙

目录 1、什么是防火墙&#xff1f; 2、iptables 3、firewalld 如何实现端口转发&#xff1f; 1、什么是防火墙&#xff1f; 防火墙&#xff1a;防火墙是位于内部网和外部网之间的屏障&#xff0c;它按照系统管理员预先定义好的规则来控制数据包的进出。防火墙又可以分为硬件…

Error: Can‘t find Python executable “python“, you can set the PYTHON env var

亲测可用&#xff0c;若有疑问请私信 此问题&#xff0c;自己分析了好久才找到问题。其实有两种解决方案&#xff0c;我这里举例了一个&#xff0c;另一种环境变量配置也是可以的。希望能帮到大家。 问题描述&#xff1a; 在执行npm install 过程中出现 V未安装 解决方案&…

Python学习笔记-PyQt6工具栏

工具栏工具栏可以有多个&#xff0c;而且可以设置不同的位置参数。4.1工具栏位置参数QtCore.Qt.ToolBarArea.LeftToolBarAreaQtCore.Qt.ToolBarArea.RightToolBarAreaQtCore.Qt.ToolBarArea.TopToolBarAreaQtCore.Qt.ToolBarArea.BottomToolBarAreaQtCore.Qt.ToolBarArea.AllTo…

库的制作相关信息

库 通过把函数进行打包&#xff0c;然后形成相应的库&#xff0c;供其他的主函数使用。 静态库 以.a进行结尾&#xff0c;把库的东西&#xff08;头与库文件进行打包到之中&#xff09;打包到可执行程序之中。 静态库不是使用相对的位置信息&#xff0c;直接的信息。 bank…

如何通过Java导出带格式的 Excel 数据到 Word 表格

在Word中制作报表时&#xff0c;我们经常需要将Excel中的数据复制粘贴到Word中&#xff0c;这样则可以直接在Word文档中查看数据而无需打开另一个Excel文件。但是如果表格比较长&#xff0c;内容就会存在一定程度的丢失&#xff0c;无法完整显示数据。并且当工作量到达一定程度…

.net6 Web Api使用JWT-从后端到前端全部过程

jwt是做验证的必经之路&#xff0c;至于原理&#xff0c;就不在叙述了&#xff0c;可以参考官网 jwt官网介绍 JSON Web Tokens - jwt.io 原理介绍 JSON Web Token 入门教程 - 阮一峰的网络日志 看完之后&#xff0c;结合这个图&#xff0c;就明白了。 本案例使用vs2022&…

从技术专家到总经理,在不确定中探索和成长

你好&#xff0c;我是石东海。 前段时间我应邀跟一些企业做过一些交流&#xff0c;探讨在这个数字化时代&#xff0c;怎么去解决技术团队所面临的一些共性问题&#xff0c;包括技术思维转变和管理思维转变方面所经历的挑战。期间谈到了一些我个人的经历&#xff0c;以及这两年…

哈希表(一)—— 闭散列 / 开放地址法的模拟实现

哈希表的基本思路是通过某种方式将某个值映射到对应的位置&#xff0c;这里的采取的方式是除留余数法&#xff0c;即将原本的值取模以后再存入到数组的对应下标&#xff0c;即便存入的值是一个字符串&#xff0c;也可以根据字符串哈希算法将字符串转换成对应的ASCII码值&#x…

这家十年磨剑的企业级存储厂商,为什么将分布式块存储也开源了?

只要提到企业级存储&#xff0c;任何成功的厂商无不以十年为单位的积累&#xff0c;才能实现真正的创新。当然&#xff0c;作为存储领域相对更为复杂的分布式块存储&#xff0c;存储创新公司一般都不太愿意碰它。原因很简单&#xff0c;在技术自研的道路上&#xff0c;更需要坐…

Nginx之限流

文章目录Nginx如何限流配置基本的限流处理突发无延迟的排队高级配置示例location包含多limit_req指令配置相关功能发送到客户端的错误代码指定location拒绝所有请求总结流量限制(rate-limiting)&#xff0c;是 Nginx 中一个非常实用&#xff0c;却经常被错误理解和错误配置的功…

JavaScript 数据处理 · 基本统计(文末附视频)

第 5 节 基本数据处理 基本统计 学习了如何对 JavaScript 中的数组数据进行操作之后&#xff0c;我们就要回到刚开始选择购买这本小册的目的了&#xff1a;使用 JavaScript 开发灵活的数据应用。既然说是数据应用&#xff0c;那么便离不开统计计算&#xff0c;而数组就可以说…

Android 设备自动重启分析[低内存]——MTK平台 debuglogger

大家有没有遇到和我一样的问题&#xff0c;android设备(我这里android 平板)用着用着突然就黑屏自动重启了&#xff0c;重启后一切正常&#xff0c;这个问题还是概率性的&#xff0c;复现都不好复现... 本人公司是做平板定制的&#xff0c;主要针对平板进行上网限制&#xff0c…

C语言进阶——字符函数

目录 一.前言 二.strlen 1.函数介绍 2.三种模拟实现 三.长度不受限制函数 1.strcpy 模拟实现 2.strcat 模拟实现 3.strcmp 模拟实现 四.长度受限制函数 1.strncpy 模拟实现 2.strncat 模拟实现 3.strncmp 模拟实现 五.字符串查找 1.strstr 模拟实现 2.st…

手把手教你快速搞定4个职场写作场景

“ 【写作能力提升】系列文章&#xff1a; 为什么建议你一定要学会写作? 手把手教你快速搞定 4 个职场写作场景 5 种搭建⽂章架构的⽅法”免费赠送! ”一、前言 Hello&#xff0c;我是小木箱&#xff0c;今天主要分享的内容是: 写作小白需要避免的五个写作误区和灵魂五问。 二…

好家伙谷歌翻译又不能用了(有效解决方法)

今天打开idea想翻译单词发现谷歌翻译又又又挂了。为什么挂掉&#xff0c;可能是那个ip节点太多人用了&#xff0c;我也不懂我就是一个小白。不bb了说一下解决方法。一、手动Ping可以连接的ip这里我使用的是&#xff1a;https://ping.chinaz.com然后我们输入&#xff1a; transl…

YoloV8简单使用

我们坐在阳光下&#xff0c;我们转眼间长大&#xff0c;Yolo系列都到V8了&#xff0c;来看看怎么个事。目标检测不能没有Yolo&#xff0c;就像西方不能没有耶路撒冷。这个万能的目标检测框架圈粉无数&#xff0c;经典的三段式改进也是改造出很多论文&#xff0c;可惜我念书时的…

论文投稿指南——中文核心期刊推荐(植物学)

【前言】 &#x1f680; 想发论文怎么办&#xff1f;手把手教你论文如何投稿&#xff01;那么&#xff0c;首先要搞懂投稿目标——论文期刊 &#x1f384; 在期刊论文的分布中&#xff0c;存在一种普遍现象&#xff1a;即对于某一特定的学科或专业来说&#xff0c;少数期刊所含…