【lesson57】信号量和生产者消费者模型(环形队列版)

news2025/1/10 1:53:03

文章目录

  • 信号量概念
  • 信号量接口
    • 初始化
    • 销毁
    • 等待
    • 发布
  • 基于环形队列的生产者消费者模型
  • 编码
    • Common.h
    • LockGuard.hpp
    • Task.hpp
    • sem.hpp
    • RingQueue.hpp
    • ConProd.cc

信号量概念

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

什么是信号量?
共享资源—>保证任何时刻都只有一个执行流进行访问----->也就有了之前的临界资源,临界区的概念

之前的互斥加锁—>将共享资源当做整个使用。
但是不同的线程可能访问同一个共享资源的不同区域,这样如何我们加个整体锁必定带来整个进行效率的降低。

如果一个共享资源不当做一个整体,而让不同的执行流访问不同的区域的话,那么不就提高了并发度吗?是的。
而只有当不同的线程访问同一个区域的时候我们再进行同步与互斥。

两个问题
1.我们怎么知道一整个共享区,一共有多少个资源,还剩多少个资源?
2.我们怎么保证这个资源就是给我们的?(程序员编码保证)我们怎么知道线程一定可以具有一个共享资源(信号量保证)

电影院买票的例子:
买票的本质:叫做资源(电影院座位)的预订机制

信号量本质:是一个计数器,访问临界资源的时候,必须先申请资源(sem–预订资源),使用完毕后信号量资源必须释放(sem++)
如何理解信号量的使用—>我们申请一个信号量---->当前执行流一定具有一个资源,可以被它使用—>是哪个资源呢?---->需要程序员结合场景,自定义编码完成。

信号量接口

初始化

int sem_init(sem_t *sem, int pshared, unsigned int value);
//参数:
//pshared:0表示线程间共享,非零表示进程间共享
//value:信号量初始值

销毁

 int sem_destroy(sem_t *sem);

等待

//功能:等待信号量,会将信号量的值减1
int sem_wait(sem_t *sem);

发布

//功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1。
int sem_post(sem_t *sem);

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

在这里插入图片描述
环形队列采用数组模拟,用模运算来模拟环状特性环形结构起始状态和结束状态都是一样的不好判断为空或者为满,所以可以通过加计数器或者标记位来判断满或者空。另外也可以预留一个空的位置,作为满的状态
但是我们现在有信号量这个计数器,就很简单的进行多线程间的同步过程
在这里插入图片描述
什么时候生产者和消费者会访问同一个位置?
在这里插入图片描述
1.环形队列空的时候,生产者和消费者会访问同一个位置
2.环形队列满的时候,生产者和消费者会访问同一个位置
3.其它情况不会访问同一个位置。

那么该如何解决?
1.用计数器解决
2.专门浪费一个格子解决

如果生产者和消费者指向了环形结构的同一个位置;那么生产者和消费者要有互斥或者同步的问题,不然生产者和消费者指向的都是不同的位置。

当生产者和消费者指向同一个位置,具有互斥同步关系就可以。
当生产者和消费者不指向同一个位置想让他们并发执行呢?
期望:
1.生产者不能将消费者套圈。
2.消费者超过生产者
3.环形队列为空:一定要让生产者先行
4.环形队列为满:一定要让消费者先行

怎么编码保证?
生产者:最关注的是空间资源
消费者:最关注的是空间里面的数据资源
最开始计数器值:
生产者:spaceSem---->为最大的N
消费者:dataSem---->为0

生产者:生产一个数据
P(spaceSem)—>spaceSem- -
V(dataSem)----->dataSem++
消费者:消费一个数据
P(dataSem)—>dataSem- -
V(spaceSem)----->spaceSem++

编码

Common.h

#pragma once
#include <iostream>
#include <array>
#include <functional>
#include <ctime>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>



#define RingQueueNum 5//环形队列空间大小
#define CONSUMRT_NUM 2//生产者个数
#define PRODUCTER_NUM 2//消费者个数
//生产者和消费者个数可以自定义

LockGuard.hpp

#pragma once
#include "Common.h"

//自己封装的锁,可以自动初始化和销毁
class Mutex
{
public:
    Mutex()
    {
        pthread_mutex_init(&_mtx,nullptr);
    }

    pthread_mutex_t& GetMutex()
    {
        return _mtx;
    }

    ~Mutex()
    {
        pthread_mutex_destroy(&_mtx);
    }
private:
    pthread_mutex_t _mtx;
};

//自己封装RAII风格的lock和unlock,会自动解锁解锁
class LockGuard
{
public:
    LockGuard(Mutex* mtx)
    :_mtx(mtx)
    {
        pthread_mutex_lock(&_mtx->GetMutex());
        //std::cout << "lock" << std::endl;
    }


    ~LockGuard()
    {
        pthread_mutex_unlock(&_mtx->GetMutex());
        //std::cout << "unlock" << std::endl;
    }
private:
    Mutex* _mtx;
};

Task.hpp

#pragma once
#include "Common.h"

typedef std::function<int(int, int)> func_t;

//自己写的任务
class Task
{
public:
    Task()
    {}
    
    Task(int x, int y, func_t func)
        : _x(x),
          _y(y),
          _func(func)
    {}

    int operator()()
    {
        return _func(_x, _y);
    }

public:
    int _x;
    int _y;
    func_t _func;
};

sem.hpp

#pragma once
#include "Common.h"

//自己封装一个信号量P(申请信号量),V(释放信号量)
class Sem
{
public:
    Sem(int value = RingQueueNum)
    {
        sem_init(&_sem,0,value);
    }

    void P()
    {
        sem_wait(&_sem);
    }

    void V()
    {
        sem_post(&_sem);
    }

    ~Sem()
    {
        sem_destroy(&_sem);
    }
private:
    sem_t _sem;
};

RingQueue.hpp

#pragma once
#include "Common.h"
#include "LockGuard.hpp"
#include "Sem.hpp"

template<class T>
class RingQueue
{
public:
    RingQueue()
    :_space_sem(RingQueueNum),
     _data_sem(0)
    {}
    void Push(T& in)
    {
    	//先申请信号量
        _space_sem.P();
        LockGuard lock(&_producter_mtx);
        _rq[product_index++] = in;
        product_index %= RingQueueNum;
        _data_sem.V();
    }
    
    void Pop(T* out)
    {
    	//先申请信号量
        _data_sem.P();
        LockGuard lock(&_cosumer_mtx);
        *out = _rq[consumer_index++];
        consumer_index %= RingQueueNum;
        _space_sem.V();
    }
private:
    std::array<T,RingQueueNum> _rq;//环形队列,本质是数组
    int consumer_index = 0;//消费者数组下标
    int product_index = 0;//生产者数组下标
    Mutex _cosumer_mtx;//消费者锁
    Mutex _producter_mtx;//生产者锁

    Sem  _space_sem;//空间资源信号量
    Sem  _data_sem;//数据资源信号量
};

ConProd.cc

#include "Common.h"
#include "RingQueue.hpp"
#include "Task.hpp"

int myadd(int x, int y)
{
    return x + y;
}
void *ConsumerRoutine(void *args)
{
    RingQueue<Task> *rq = (RingQueue<Task> *)args;
    while (true)
    {
    	//获取任务
        Task t;
        rq->Pop(&t);
		
		//执行任务
        std::cout << pthread_self() << "|Get a Task: " << t._x << " + " << t._y << "=" << t() << std::endl;
        sleep(1);
    }
}
void *ProducterRoutine(void *args)
{
    RingQueue<Task> *rq = (RingQueue<Task> *)args;

    while (true)
    {
    	//制作任务
        int x = rand() % 100 + 1;
        int y = rand() % 100 + 1;
        Task t(x, y, myadd);
        std::cout << pthread_self() << "|Make a Task: " << x << " + " << y << "=?" << std::endl;
        //发送任务
        rq->Push(t);
        sleep(1);
    }
}
int main()
{
    srand((unsigned int)time(nullptr) ^ getpid() ^ 0x77777);
	
	//定义线程
    pthread_t consumer[CONSUMRT_NUM];
    pthread_t producter[PRODUCTER_NUM];
	
	//定义环形队列
    RingQueue<Task> *ring_queue = new RingQueue<Task>;

	//创建消费者线程
    for (int i = 0; i < CONSUMRT_NUM; i++)
    {
        pthread_create(consumer + i, nullptr, ConsumerRoutine, (void *)ring_queue);
    }
	
	//创建生产者线程
    for (int i = 0; i < PRODUCTER_NUM; i++)
    {
        pthread_create(producter + i, nullptr, ProducterRoutine, (void *)ring_queue);
    }
	
	//等待消费者线程
    for (int i = 0; i < CONSUMRT_NUM; i++)
    {
        pthread_join(consumer[i], nullptr);
    }

	//等待生产者线程
    for (int i = 0; i < PRODUCTER_NUM; i++)
    {
        pthread_join(producter[i], nullptr);
    }

    return 0;
}

多生产和多消费的意义在哪?
提高线程的并发度。
信号量的本质是一个计数器—>计数器的意义是什么?
以前:申请锁—>判断与访问---->释放锁—>本质我们是不清楚临界资源的情况
现在:信号量要提前预定资源的情况,而在PV变化过程中,我们可以在外部就能知晓临界资源的情况。

计数器的意义:可以不用进入临界区,就可以得知临界资源的情况,甚至可以减少内部资源的判断。

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

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

相关文章

Python 使用 raise 语句抛出异常

在 Python 编程中&#xff0c;异常处理是至关重要的一部分。异常能够帮助程序在面对错误和意外情况时进行适当的处理&#xff0c;从而使程序具有更好的稳定性和可靠性。而 raise 语句则是 Python 中用来手动触发异常的关键工具之一。本文将探讨 Python 中 raise 语句的使用方法…

算法--数论二

这里写目录标题 高斯消元高斯消元求线性方程组用途高斯消元的数学思想例题代码 二级目录 一级目录二级目录二级目录二级目录 一级目录二级目录二级目录二级目录 一级目录二级目录二级目录二级目录 一级目录二级目录二级目录二级目录 高斯消元 高斯消元求线性方程组 用途 这个…

【机器学习案例5】语言建模 - 最常见的预训练任务一览表

自监督学习 (SSL) 是基于 Transformer 的预训练语言模型的支柱,该范例涉及解决有助于建模自然语言的预训练任务 (PT)。本文将所有流行的预训练任务放在一起,以便我们一目了然地评估它们。 SSL 中的损失函数 这里的损失函数只是模型训练的各个预训练任务损失的加权和。 以BE…

CSP-201909-1-小明种苹果

CSP-201909-1-小明种苹果 #include <iostream> using namespace std; int main() {long long sumApple 0, maxNum 0, maxAppleNum 0, n, m;cin >> n >> m;for (long long i 0; i < n; i){long long appleNum, delta 0;cin >> appleNum;for (l…

Kibana:如何嵌入 Kibana 仪表板

作者&#xff1a;Carly Richmond 像我这样的前端工程师经常提出的要求是将 Kibana 等来源的现有仪表板嵌入到 JavaScript Web 应用程序中。 这是我必须多次执行的任务&#xff0c;因为我们希望快速部署用户生成的视图或允许用户控制给定的视图。 从我们从精彩的开发者社区收到的…

模拟电子技术——分压式偏置放大电路、多级放大电路、差动放大电路、互补输出级

文章目录 前言基本放大电路链接&#xff0c;上一篇 [基本放大电路](https://blog.csdn.net/weixin_47541751/article/details/136112075?spm1001.2014.3001.5502) 一、分压式偏置放大电路什么是分压式偏置电路分压式电路组成电路分析估算静态工作点 二、多级放大电路什么是多级…

【方法】如何打开带密码的RAR分卷压缩文件?

RAR分卷文件是一种特殊的RAR压缩文件格式&#xff0c;也就是将文件压缩成多个相同大小的压缩包&#xff0c;可以更方便传输。那如果收到了带有密码的RAR分卷压缩文件&#xff0c;要如何打开呢&#xff1f; 无论RAR分卷压缩文件是否设置了密码保护&#xff0c;在打开或者解压分…

【题解】数的范围(二分模板)

笔记 if (check(mid)) L mid &#xff0c;则 mid L R 1 >> 1 if (check(mid)) R mid &#xff0c;则 mid L R >> 1 题目 #include<bits/stdc.h> using namespace std;int n, q; int a[100010]; int b[10010]; typedef pair<int, int> PII;v…

html的列表标签

列表标签 列表在html里面经常会用到的&#xff0c;主要使用来布局的&#xff0c;使其整齐好看. 无序列表 无序列表[重要]&#xff1a; ul &#xff0c;li 示例代码1&#xff1a; 对应的效果&#xff1a; 无序列表的属性 属性值描述typedisc&#xff0c;square&#xff0c;…

java-8组合式异步编程

11.1 Future 接口 Future接口在Java5中被引人&#xff0c;设计初衷是对将来某个时刻会发生的结果进行建模。它建模了一种异步计算&#xff0c;返回一个执行运算结果的引用&#xff0c;当运算结束后&#xff0c;这个引用被返回给调用方。在Future中触发那些潜在耗时的操作把调用…

LEETCODE 164. 破解闯关密码

class Solution { public:string crackPassword(vector<int>& password) {vector<string> password_str;for(int i0;i<password.size();i){password_str.push_back(to_string(password[i]));}//希尔排序int gappassword.size()/2;while(gap>0){for(int i…

Netty Review - 底层零拷贝源码解析

文章目录 Pre概述源码解析入口索引AbstractNioByteChannel.NioByteUnsafe#readallocHandle.allocate(allocator) 小结传统的零拷贝 Pre Netty Review - 直接内存的应用及源码分析 概述 Netty 的零拷贝技术是通过优化数据传输过程中的数据复制操作&#xff0c;以降低系统的开销…

Kotlin基本语法 3 类

1.定义类 package classStudyclass Player {var name:String "jack"get() field.capitalize()set(value) {field value.trim()} }fun main() {val player Player()println(player.name)player.name " asdas "println(player.name)} 2.计算属性与防范…

Matlab|基于支持向量机的电力短期负荷预测【三种方法】

目录 主要内容 部分代码 结果一览 下载链接 主要内容 该程序主要是对电力短期负荷进行预测&#xff0c;采用三种方法&#xff0c;分别是最小二乘支持向量机&#xff08;LSSVM&#xff09;、标准粒子群算法支持向量机和改进粒子群算法支持向量机三种方法对负荷进行…

Codeforces Round 925 (Div. 3) D. Divisible Pairs (Java)

Codes Round 925 (Div. 3) D. Divisible Pairs (Java) 比赛链接&#xff1a;Codeforces Round 925 (Div. 3) D题传送门&#xff1a;D.Divisible Pairs 题目&#xff1a;D.Divisible Pairs 题目描述 输出格式 For each test case, output a single integer — the number o…

【Windows】MacOS制作纯净版Windows10安装U盘

方法一、在window系统中更新win10&#xff08;不更新引导程序&#xff09; cp -rp /Volumes/Windows10专业版\ 64位/* /Volumes/WIN10/https://baijiahao.baidu.com/s?id1760695844372493842&wfrspider&forpc 方法二、在window系统中更新win10&#xff08;更新引导程…

C语言学习day15:数组强化训练

题目一&#xff1a; 称体重&#xff1a;分别给10个值&#xff0c;来获得最大值 思路&#xff1a; 定义数组&#xff0c;给数组内赋10个值第一个下标的值与第二个下标的值进行比较定义max&#xff0c;将比较得来的较大的值赋值给max一直比较直到比较到最后一个下标&#xff0…

JavaScript中null和undefined的区别

JavaScript中null和undefined是两个特殊的值&#xff0c;经常在编程中遇到。虽然它们经常被混淆&#xff0c;但它们有着不同的含义和用法。本文将详细介绍JavaScript中null和undefined的区别&#xff0c;帮助开发者更好地理解和使用它们。 首先&#xff0c;让我们来了解一下nu…

React入门到精通:掌握前端开发的必备技能!

介绍&#xff1a;React是一个由Facebook开发和维护的JavaScript库&#xff0c;用于构建用户界面&#xff0c;特别是用于构建单页应用程序和移动应用程序的用户界面。以下是对React的详细介绍&#xff1a; 虚拟DOM&#xff1a;React通过使用虚拟DOM&#xff08;Document Object …

蓝桥杯第九届电子类单片机组程序设计(模拟题)

目录 蓝桥杯大赛历届真题 一、第九届比赛题 二、代码实现 main.c iic.c iic.h 前言 蓝桥杯的真题可以再官网上查到&#xff0c;链接放下边了&#xff0c;点击即可跳转到官网&#xff1a; 蓝桥杯大赛历届真题 突然发现官网上的题也不全&#xff0c;而且还有一部分是模拟…