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

目录

  • 一、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.ccg++ -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.hqwc.cn/news/295779.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Angular 11到升级到 Angular 16

日新月异&#xff0c;与时俱进… 随着Angular版本不断更新&#xff0c;再看所开发的项目版本仍然是Angular 11&#xff0c;于是准备升级 截止发博日最版本是 v17.1.0&#xff0c;考虑到稳定性因素决定升级到v16版本 一&#xff1a;查看 升级指南 二&#xff1a;按照指南&…

【Element】el-table 使用 el-table-infinite-scroll 插件实现滚动加载

虽然 el 官方提供了 Infinite Scroll 无限滚动 组件 但是却不支持 el-table 组件&#xff0c;这就很难受了&#xff0c;还好已经有大佬写好了插件&#xff0c;并且支持 element-plus/infinite-scroll 组件的所有选项。 el-table-infinite-scroll el-table-infinite-scroll 看…

「微服务模式」七种微服务反模式

什么是微服务 流行语经常为进化的概念提供背景&#xff0c;并且需要一个良好的“标签”来促进对话。微服务是一个新的“标签”&#xff0c;它定义了我个人一直在发现和使用的领域。文章和会议描述了一些事情&#xff0c;我慢慢意识到&#xff0c;过去几年我一直在发展自己的个人…

HarmonyOS的装饰器之BuilderParam 理解

BuilderParam 装饰器 使用时间&#xff1a;当定义了一个子组件&#xff0c;并且子组件的build()中有一个布局在不同的父组件&#xff0c;实现效果不一样的时候&#xff0c;可以在子组件中用这个BuilderParam装饰器&#xff0c; 在父组件用Builder 装饰器进行实现&#xff0c;然…

Netty-2-数据编解码

解析编解码支持的原理 以编码为例&#xff0c;要将对象序列化成字节流&#xff0c;你可以使用MessageToByteEncoder或MessageToMessageEncoder类。 这两个类都继承自ChannelOutboundHandlerAdapter适配器类&#xff0c;用于进行数据的转换。 其中&#xff0c;对于MessageToMe…

【MybatisPlus快速入门】(3)SpringBoot整合MybatisPlus 之 Lombok插件安装及MybatisPlus分页代码示例

目录 1.Lombok1.1 步骤1:添加lombok依赖 2.2 步骤2:安装Lombok的插件1.3 步骤3:模型类上添加注解2 分页功能2.1 步骤1:调用方法传入参数获取返回值2.2步骤2:设置分页拦截器2.3 步骤3:运行测试程序 之前我们已学习MyBatisPlus在代码示例与MyBatisPlus的简介&#xff0c;在这一节…

frp配置多端口内网穿透?frp多端口怎么配置?

使用frp作为内网穿透的时候&#xff0c;有时候&#xff0c;我们需要多个端口穿透&#xff0c;怎么配置呢&#xff1f; 更有frp相关&#x1f449;&#xff1a;凯哥个人博客&#xff0c;搜索frp 思考&#xff1a; 我们知道frp要想内网穿透&#xff0c;需要配置服务端和客户端。…

【智慧办公】如何让智能会议室的电子标签实现远程、批量更新信息?东胜物联网硬件网关让解决方案更具竞争力

近年来&#xff0c;为了减少办公耗能、节能环保、降本增效&#xff0c;越来越多的企业开始从传统的办公模式转向智慧办公。 以智能会议室为例&#xff0c;会议是企业业务中不可或缺的一部分&#xff0c;但在传统办公模式下&#xff0c;一来会议前行政人员需要提前准备会议材料…

房顶漏水啦【算法赛】

问题描述 小蓝家的房顶是一个边长为 n 的正方形&#xff0c;可以看成是由 nn 个边长为 1 的小正方形格子组成。 从上到下第 i 行、从左到右第 j 列的格子用 (i,j) 表示。 小蓝的家由于年久失修&#xff0c;导致房顶有一些地方漏水。总共有 m 处漏水的地方&#xff0c;我们用…

2023年都找不到工作,软件测试已经崩了?

最近后台很多粉丝给我留言&#xff1a; 2023年软件测试已经崩盘了吗&#xff0c;为什么都找不到工作了&#xff1f; 确实&#xff0c;今年经济大环境不好&#xff0c;企业也都在降本增效&#xff0c;如果技术能力还在被应届生竞争岗位的阶段&#xff0c;只会越来越难。 找不…

【XML】TinyXML 详解(二):接口详解

【C】郭老二博文之&#xff1a;C目录 1、XML测试文件&#xff08;laoer.xml&#xff09; <?xml version"1.0" standalone"no" ?> <!-- Hello World !--> <root><child name"childName" id"1"><c_child…

自学华为鸿蒙开发?一般人我还是劝你算了吧!!!

本人纯屌丝一枚&#xff0c;在学编程之前对电脑的认知也就只限于上个网&#xff0c;玩个办公软件。这里不能跑题&#xff0c;我为啥说自学鸿蒙开发&#xff0c;一般人我还是劝你算了吧。因为我就是那个一般人。 基础真的很简单&#xff0c;是个人稍微认点真都能懂&#xff0c;…