Linux多线程:POSIX信号量,基于信号量的环形队列实现生产者消费者模型

news2025/1/16 20:19:00

目录

  • 一、POSIX信号量
    • 1.1 初始化信号量
    • 1.2 销毁信号量
    • 1.3 等待信号量
    • 1.4 发布信号量
    • 1.5 基于环形队列的生产消费模型(用信号量控制生产者和消费者之间的同步互斥关系)
      • 1.5.1 makefile
      • 1.5.2 RingQueue.hpp
      • 1.5.3 Sem.hpp
      • 1.5.4 Task.hpp
      • 1.5.5 main.cc
  • 二、信号量控制的环形队列原理图

一、POSIX信号量

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

什么是信号量?信号量的本质就是一把计数器。

1.1 初始化信号量

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

1.2 销毁信号量

int sem_destroy(sem_t *sem);

1.3 等待信号量

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

1.4 发布信号量

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

上一个生产者-消费者的例子是基于queue的,其空间可以动态分配,现在基于固定大小的环形队列重写这个程序
(POSIX信号量)。

1.5 基于环形队列的生产消费模型(用信号量控制生产者和消费者之间的同步互斥关系)

环形队列采用数组模拟,用模运算来模拟环状特性。
在这里插入图片描述
但是我们现在有信号量这个计数器,就很简单的进行多线程间的同步过程。

1.5.1 makefile

ring_queue:main.cc
	g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
	rm -f ring_queue

1.5.2 RingQueue.hpp

#pragma once

#include <iostream>
using namespace std;

#include <pthread.h>
#include <vector>
#include <ctime>
#include <unistd.h>
#include "Sem.hpp"

static const int default_num = 5;

// 用数据模拟环形队列
template <class T>
class Ring_Queue
{
public:
    Ring_Queue(int num = default_num)
        : _rq(num)
        , _num(num)
        , _p_step(0)
        , _c_step(0)
        , _space_sem(num) // 刚开始空间信号量量等于队列的长度
        ,_data_sem(0) // 刚开始数据信号量等于0,因为没有数据
    {
        pthread_mutex_init(&_p_mtx, nullptr);
        pthread_mutex_init(&_c_mtx, nullptr);
    }

    ~Ring_Queue()
    {
        pthread_mutex_destroy(&_p_mtx);
        pthread_mutex_destroy(&_p_mtx);
    }

    void Push(const T &in)
    {
        //这里为啥要先申请信号量,然后再加锁呢?为了提高并发度,提高效率
        //因为我们知道加锁和解锁之间的代码,即临界区的代码是越少越好的,
        //而如果先申请锁,那么每次只有一个线程申请信号量,然后执行临界区的
        //代码,等到这个线程解锁后,其它线程才能申请锁,然后申请信号量,
        //这就会导致线程在运行临界区代码的时候其它线程是阻塞在锁外面的,而
        //在锁外面又申请不到信号量,那就是白白地等,但是如果是先申请信号量,
        //然后再申请锁,那么当一个线程在临界区执行代码的时候,其它的线程也
        //可以同步地申请信号量,从而提高了并发度,等到锁释放后,其它的线程
        //只需要申请到锁就可以访问临界区的代码了,而不用在申请到锁之后才申
        //请信号量

        // 插入数据之前要申请空间信号量
        _space_sem.p();

        pthread_mutex_lock(&_p_mtx);
        _rq[_p_step++] = in;
        _p_step %= _num; // 体现环形结构
        pthread_mutex_unlock(&_p_mtx);

        // 插入了数据之后,这个数据并不是立刻就可以被删除的,即
        // 插入数据不是目的,被读取才是目的,所以插入数据之后应该
        // 释放_data_sem,即插入数据之后数据信号量应该++,而不是
        // 空间信号量++
        _data_sem.v();
    }

    void Pop(T &out)
    {
        //先申请信号量的原因同上
        _data_sem.p();

        pthread_mutex_lock(&_c_mtx);
        out = _rq[_c_step++];
        _c_step %= _num;//体现环形结构
        pthread_mutex_unlock(&_c_mtx);

        // 同理,这里要释放空间信号量
        _space_sem.v();
    }

private:
    vector<T> _rq; // 环形队列
    int _num;      // 环形队列的大小

    int _p_step; // 生产者生产的下标
    int _c_step; // 消费者消费的下标

    Sem _space_sem; // 空间信号量,这是生产者关心的
    Sem _data_sem;  // 数据信号量,这是消费者关心的

    // 锁是防止多线程访问环形队列时出现数据不一致问题

    //多生产多消费的场景下需要加这两把锁,那如何分析得出多生产多消费
    //场景下是加这两把锁维护的呢???
    //首先我们要想的不是如何加锁的问题,而是要想我要维护什么更关系的问题,
    //比单生产单消费我多维护了什么关系的问题。
    //原来的单生产单消费中只有生产者和消费者之间的关系,就是同步和互斥的关系
    //现在多生产多消费很明显新增了生产者和生产者,消费者和消费者这两对关系,
    //这两对关系都是互斥关系,所以我们要多维护了两对互斥关系,即生产和生产,消费和消费的关系。
    //所以我们可以给生产者们加一把锁,给消费者们也加一把锁,需要加锁才能访问的资源是
    //临界资源,那么生产者们的临界资源是什么?其实就是_p_step下标,所有的生产者都是
    //想抢到_p_step下标,从而向该下标位置放入数据,同理消费者们的临界资源就是_c_step下标,
    //所以如果是多生产多消费,我们应该加两把锁,保护_p_step和_c_step临界资源

    // 这把锁的本质是锁住生产者的下标,保证访问下标时不会出错
    pthread_mutex_t _p_mtx;
    // 这把锁的本质是锁住消费者的下标,保证访问下标时不会出错
    pthread_mutex_t _c_mtx;
};

1.5.3 Sem.hpp

#include <semaphore.h>


class Sem
{
public:
    Sem(int num)
    {
        //初始化信号量
        sem_init(&_sem,0,num);
    }
    ~Sem()
    {
        //销毁信号量
        sem_destroy(&_sem);
    }

    void p()
    {
        //p操作就是获取一个信号量,该操作保证是原子的
        sem_wait(&_sem);
    }

    void v()
    {
        //v操作是释放一个信号量,该操作保证是原子的
        sem_post(&_sem);
    }
private:
    sem_t _sem;//信号量
};

1.5.4 Task.hpp

#include <functional>
using namespace std;
#include <string>


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

class Task
{
public:
    Task()
    {}

    Task(int x, int y, char op,func_t func)
        : _x(x), _y(y), _op(op), _func(func)
    {}

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

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

1.5.5 main.cc

#include "RingQueue.hpp"

#include <cmath>

pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;



// void *producter(void *args)
// {
//     Ring_Queue<Task> *rq = static_cast<Ring_Queue<Task> *>(args);
//     while (true)
//     {
//         int x = 0;
//         int y = 0;
//         char op;

//         cout << "Please input the first num : ";
//         cin >> x;
//         cout << "Please input the second num : ";
//         cin >> y;

//         Task t(x, y, op, Add);
//         rq->Push(t);
//         printf("生产者生产了一个数据:%d %c %d = ?\n", x, op, y);

//         usleep(1000);
//     }
//     return nullptr;
// }

int main()
{
    srand((unsigned int)time(nullptr));
    pthread_t p,c;
    Ring_Queue<int>* rq=new Ring_Queue<int>(10);

    pthread_create(&p,nullptr,producter,rq);
    pthread_create(&c,nullptr,consumer,rq);

    pthread_join(p,nullptr);
    pthread_join(c,nullptr);

    return 0;
}

// //单生产单消费
// int Add(int x, int y)
// {
//     return x + y;
// }

// int Sub(int x, int y)
// {
//     return x - y;
// }

// int Mul(int x, int y)
// {
//     return x * y;
// }

// int Div(int x, int y)
// {
//     return x / y;
// }

// int Mol(int x, int y)
// {
//     return x % y;
// }

// #include "Task.hpp"

// void *producter(void *args)
// {
//     Ring_Queue<Task> *rq = static_cast<Ring_Queue<Task> *>(args);
//     while (true)
//     {

//         int x = 0;
//         int y = 0;
//         char op;

//         pthread_mutex_lock(&mtx);
//         usleep(1000);
//         cout << "Please input the first num : ";
//         cin >> x;
//         cout << "Please input the second num : ";
//         cin >> y;
//         cout << "Please input the operator : ";
//         cin >> op;
//         printf("生产者 [%d] 生产了一个数据:%d %c %d = ?\n", pthread_self(), x, op, y);
//         pthread_mutex_unlock(&mtx);

//         switch (op)
//         {
//         case '+':
//         {
//             Task t(x, y, op, Add);
//             rq->Push(t);
//             break;
//         }
//         case '-':
//         {
//             Task t(x, y, op, Sub);
//             rq->Push(t);
//             break;
//         }
//         case '*':
//         {
//             Task t(x, y, op, Mul);
//             rq->Push(t);
//             break;
//         }
//         case '/':
//         {
//             Task t(x, y, op, Div);
//             rq->Push(t);
//             break;
//         }
//         case '%':
//         {
//             Task t(x, y, op, Mol);
//             rq->Push(t);
//             break;
//         }
//         default:
//         {
//             cout << "运算符错误!!!" << endl;
//             exit(1);
//         }
//         }
//     }
//     return nullptr;
// }


// void *consumer(void *args)
// {
//     Ring_Queue<Task> *rq = static_cast<Ring_Queue<Task> *>(args);
//     while (true)
//     {
//         sleep(1);
//         Task out;
//         rq->Pop(out);

//         printf("消费者 [%d] 消费了一个数据:", pthread_self());
//         cout << out._x << " " << out._op << " " << out._y << " = " << out() << endl;
//     }
//     return nullptr;
// }


// int main()
// {
//     srand((unsigned int)time(nullptr));
//     pthread_t p, c;
//     Ring_Queue<Task> *rq = new Ring_Queue<Task>(10);

//     pthread_create(&p, nullptr, producter, rq);
//     pthread_create(&c, nullptr, consumer, rq);

//     pthread_join(p, nullptr);
//     pthread_join(c, nullptr);

//     return 0;
// }


// //多生产多消费
// int Add(int x, int y)
// {
//     return x + y;
// }

// int Sub(int x, int y)
// {
//     return x - y;
// }

// int Mul(int x, int y)
// {
//     return x * y;
// }

// int Div(int x, int y)
// {
//     return x / y;
// }

// int Mol(int x, int y)
// {
//     return x % y;
// }

// void *producter(void *args)
// {
//     Ring_Queue<Task> *rq = static_cast<Ring_Queue<Task> *>(args);
//     while (true)
//     {

//         int x = 0;
//         int y = 0;
//         char op;

//         pthread_mutex_lock(&mtx);
//         usleep(1000);
//         cout << "Please input the first num : ";
//         cin >> x;
//         cout << "Please input the second num : ";
//         cin >> y;
//         cout << "Please input the operator : ";
//         cin >> op;
//         printf("生产者 [%d] 生产了一个数据:%d %c %d = ?\n", pthread_self(), x, op, y);
//         pthread_mutex_unlock(&mtx);

//         switch (op)
//         {
//         case '+':
//         {
//             Task t(x, y, op, Add);
//             rq->Push(t);
//             break;
//         }
//         case '-':
//         {
//             Task t(x, y, op, Sub);
//             rq->Push(t);
//             break;
//         }
//         case '*':
//         {
//             Task t(x, y, op, Mul);
//             rq->Push(t);
//             break;
//         }
//         case '/':
//         {
//             Task t(x, y, op, Div);
//             rq->Push(t);
//             break;
//         }
//         case '%':
//         {
//             Task t(x, y, op, Mol);
//             rq->Push(t);
//             break;
//         }
//         default:
//         {
//             cout << "运算符错误!!!" << endl;
//             exit(1);
//         }
//         }
//     }
//     return nullptr;
// }

// void *consumer(void *args)
// {
//     Ring_Queue<Task> *rq = static_cast<Ring_Queue<Task> *>(args);
//     while (true)
//     {
//         sleep(1);
//         Task out;
//         rq->Pop(out);

//         printf("消费者 [%d] 消费了一个数据:", pthread_self());
//         cout << out._x << " " << out._op << " " << out._y << " = " << out() << endl;
//     }
//     return nullptr;
// }

// int main()
// {
//     srand((unsigned int)time(nullptr));
//     pthread_t p[3], c[2];
//     Ring_Queue<Task> *rq = new Ring_Queue<Task>(10);

//     for (int i = 0; i < 3; i++)
//     {
//         pthread_create(&p[i], nullptr, producter, rq);
//     }
//     for (int i = 0; i < 2; i++)
//     {
//         pthread_create(&c[i], nullptr, consumer, rq);
//     }

//     for (int i = 0; i < 3; i++)
//     {
//         pthread_join(p[i], nullptr);
//     }

//     for (int i = 0; i < 2; i++)
//     {
//         pthread_join(c[i], nullptr);
//     }

//     return 0;
// }


void *producter(void *args)
{
    Ring_Queue<int> *rq = static_cast<Ring_Queue<int> *>(args);

    while (true)
    {
        int in = rand() % 100 + 1;
        rq->Push(in);
        pthread_mutex_lock(&mtx);
        printf("[0x%x]:",pthread_self());
        cout << "生产者生产了一个数据 in = " << in << endl;
        pthread_mutex_unlock(&mtx);
        sleep(1);
    }
}

void *consumer(void *args)
{
    Ring_Queue<int> *rq = static_cast<Ring_Queue<int> *>(args);
    while (true)
    {
        sleep(1);
        int out;
        rq->Pop(out);
        pthread_mutex_lock(&mtx);
        printf("[0x%x]:",pthread_self());
        cout << "消费者消费了一个数据 out = " << out << endl;
        pthread_mutex_unlock(&mtx);
    }
}

二、信号量控制的环形队列原理图

在这里插入图片描述
以上就是基于信号量的环形队列实现的生产者消费者模型啦,你学会了吗?如果感觉到有所帮助的话,那就点点小心心,点点关注呗,后期还会持续更新Linux系统编程的相关知识哦,我们下期见!!!

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

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

相关文章

.Net 访问电子邮箱-LumiSoft.Net,好用

序言&#xff1a; 网上找了很多关于.Net如何访问电子邮箱的方法&#xff0c;但是大多数都达不到想要的需求&#xff0c;只有一些 收发邮件。因此 花了很大功夫去看 LumiSoft.Net.dll 的源码&#xff0c;总算做出自己想要的结果了&#xff0c;果然学习诗人进步。 介绍&#xff…

Qt 开源项目

Qt 开源项目 Omniverse View链接技术介绍 QuickQanava链接技术介绍QField链接技术介绍 AtomicDEX链接技术介绍 Status-desktop链接技术介绍 Librum链接技术介绍 A Simple Cross-Platform ReaderQPrompt链接技术介绍 GCompris链接技术介绍 Scrite链接技术介绍 QSkinny链接技术介…

如何在PC上运行大模型

如何在PC上运行大模型 在PC上使用CPU运行大模型不如使用GPU高效&#xff0c;但仍然是可以实现的大模型推理。 大模型训练要求的资源更高&#xff0c;这里直接使用面向开源的Facebook’s LLaMA model(llama-2-7b-chat.Q2_K.gguf)。 连接CPU与LLaMA model的是llama.cpp。 为方便…

认识Linux背景

1.发展史 Linux从哪里来&#xff1f;它是怎么发展的&#xff1f;在这里简要介绍Linux的发展史 要说Linux&#xff0c;还得从UNIX说起 UNIX发展的历史 1968年&#xff0c;一些来自通用电器公司、贝尔实验室和麻省理工学院的研究人员开发了一个名叫Multics的特殊操作系统。Mu…

LLaMA开源大模型源码分析!

Datawhale干货 作者&#xff1a;宋志学&#xff0c;Datawhale成员 花了一晚上照着transformers仓库的LLaMA源码&#xff0c;把张量并行和梯度保存的代码删掉&#xff0c;只留下模型基础结构&#xff0c;梳理了一遍LLaMA的模型结构。 今年四月份的时候&#xff0c;我第一次接触…

第一次记录QPSK,BSPK,MPSK,QAM—MATLAB实现

最近有偶然的机会学习了一次QPSK防止以后忘记又得找资料&#xff0c;这里就详细的记录一下 基于 QPSK 的通信系统如图 1 所示&#xff0c;QPSK 调制是目前最常用的一种卫星数字和数 字集群信号调制方式&#xff0c;它具有较高的频谱利用率、较强的抗干扰性、在电路上实现也较为…

基于STM32单片机模拟智能电梯步进电机控制升降毕业设计3

STM32单片机模拟智能电梯步进电机控制数码管显示3 演示视频&#xff08;复制到浏览器打开&#xff09;&#xff1a; 基于STM32单片机的智能电梯控制系统模拟智能电梯步进电机控制系统设计数码管显示楼层设计/DIY开发板套件3 产品功能描述&#xff1a; 本系统由STM32F103C8T6单…

技术交底二维码的应用

二维码技术交底可以逐级落实、责任到人、有据可查、是目前最方便、实用的交底方式&#xff0c;下面我们讲解技术交底二维码的应用。 1、生成对应的技术交底二维码&#xff0c;将施工方案、技术资料、安全教育资料等内容上传到二维码里。打印出来现场粘贴&#xff0c;便于作业班…

(一)深入理解Mysql底层数据结构和算法

什么是索引 索引是帮助MySQL高效获取数据的排好序的数据结构 数据结构有哪些 数据结构模拟网站&#xff1a;Data Structure Visualization 二叉树 不适合做自增ID的数据结构。如下示意图&#xff0c;假设采用二叉树作为表自增主键ID的数据存储结果如下&#xff1a;当查询i…

行列式:方程组未知数的计算:克拉默法则

行列式&#xff1a;方程组未知数的计算 ![ ](https://img-blog.csdnimg.cn/direct/4a9c2800da3746ea95c1a3c93057d796.png)

VS Code实现“Ctr+save”保存代码自动格式化

一、下载Prettier - Code formatter插件 点击安装即可 二、配置 【1】打开文件——首选项——设置 或者左下角齿轮打开设置 【2】搜索设置框输入editor default formatter&#xff08;意思是默认格式化设置&#xff09;&#xff0c;接着下拉选中刚下好的插件名称Prettier - C…

【Vulnhub 靶场】【Corrosion: 1】【简单】【20210731】

1、环境介绍 靶场介绍&#xff1a;https://www.vulnhub.com/entry/corrosion-1,730/ 靶场下载&#xff1a;https://download.vulnhub.com/corrosion/Corrosion.ova 靶场难度&#xff1a;简单 发布日期&#xff1a;2021年07月31日 文件大小&#xff1a;7.8 GB 靶场作者&#xf…

Windows安装cnpm报错 The operation was rejected by your operating system.

Windows在安装cnpm时出现如下错误 npm ERR! The operation was rejected by your operating system. npm ERR! Its possible that the file was already in use (by a text editor or antivirus), npm ERR! or that you lack permissions to access it. npm ERR! npm ERR! If y…

[vue]Echart使用手册

[vue]Echart使用手册 使用环境Echart的使用Echart所有组件和图表类型Echart 使用方法 使用环境 之前是在JQuery阶段使用Echart&#xff0c;直接引入Echart的js文件即可&#xff0c;现在是在vue中使用,不仅仅时echarts包&#xff0c;还需要安装vue-echarts&#xff1a; "…

智能优化算法应用:基于鹈鹕算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于鹈鹕算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于鹈鹕算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.鹈鹕算法4.实验参数设定5.算法结果6.参考文献7.MA…

C语言——小细节和小知识6

一、转义字符相关 \ 反斜杠&#xff0c;转义字符中的转义序列符 \? 将?转义&#xff0c;防止他被识别成三字母词(很早的东西)中的问号 //三字母词 //??(是[ //??)是] printf("%s","??(??)"); //打印结果是[] 二、fopen函数fc…

Linux 基础指令三

一、cat命令 默认是顺序查看&#xff0c;可同时查看多个文件&#xff0c;只能看普通文件&#xff0c;不能看文件以外 使用格式: cat [选项] 文件名 常用选项 -n显示行号-b跳过空白行编号-s将所有的连续的多个空行替换为一个空行&#xff08;压缩成一个空行&#xff0…

适配器模式学习

适配器模式&#xff08;Adapter&#xff09;将一个类的接口转换成客户希望的另外一个接口。Adapter 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。 适配器模式分为类适配器模式和对象适配器模式两种&#xff0c;前者类之间的耦合度比后者高&#xff0c;且要…

在Linux安装卸载文件

目录 一、Linux系统应用程序 1.典型的应用程序的目录结构 2、常见的软件包封装类型 二、RPM软件包管理 1、RPM是什么&#xff1f; 2、rpm一般命名格式 3、RPM安装包从何而来&#xff1f;如何挂载&#xff1f; 4、挂载的注意事项: 5、目的&#xff1a;提供安装包 6、查…

初学链表(分析建立学生信息链表)

本题要求实现一个将输入的学生成绩组织成单向链表的简单函数。 #include <stdio.h> #include <stdlib.h> #include <string.h> struct stud_node { int num; char name[20]; int score; struct stud_node *next; }; struct stu…