Linux生产者消费者与信号量

news2025/2/25 4:42:39

目录

一.生产者消费者概念

二.模拟实现基于阻塞队列的生产消费模型

2.1概念

2.2构造阻塞队列 

 三.信号量

3.1原理

3.2信号量函数

3.3信号量模拟互斥功能


一.生产者消费者概念

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。

生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取。
阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
这个阻塞队列就是用来给生产者和消费者解耦的。

 生产者消费者模型是多线程同步与互斥的一个经典场景,其特点:

  • 三种关系: 生产者和生产者(互斥关系)、消费者和消费者(互斥关系)、生产者和消费者(互斥关系、同步关系)。
  • 两种角色: 生产者和消费者。(通常由进程或线程承担)
  • 一个交易场所: 通常指的是内存中的一段缓冲区。(可以自己通过某种方式组织起来)

二.模拟实现基于阻塞队列的生产消费模型

2.1概念

1.阻塞队列是会被生产者和消费者同时访问的临界资源,因此我们需要用一把互斥锁将其保护来。

2.生产者线程要向阻塞队列当中Push数据,若阻塞队列已经满了,那么此时该生产者需要进行等待,直到阻塞队列中有空间时再被唤醒。消费者线程要从阻塞队列当中Pop数据,若阻塞队列为空,那么此时该消费者需要进行等待,直到阻塞队列中有新的数据时再被唤醒。

3.需要用到两个条件变量,一个条件变量用来描述队列为空,另一个条件变量用来描述队列已满。若是队列满了,生产者要进行等待,若是队列为空,消费者要进行等待。

4.当生产者或者消费者执行后,都要去唤醒另一方。

2.2构造阻塞队列 

BlockQueue.hpp代码:

#include <iostream>
#include <queue>
#include <cstdlib>
#include <unistd.h>
#include <pthread.h>

using namespace std;

const int capacity=5;
template<class T>
class BlockQueue
{
  public:
    BlockQueue(int size=capacity)//初始化
    :size_(size)
    {
      pthread_mutex_init(&mutex_,nullptr);
      pthread_cond_init(&proCond_,nullptr);
      pthread_cond_init(&conCond_,nullptr);
    }
    ~BlockQueue()//变量的销毁
    {
      pthread_mutex_destroy(&mutex_);
      pthread_cond_destroy(&proCond_);
      pthread_cond_destroy(&conCond_);
    }

    void _push(const T in)
    {
         lockQueue();//生产之前先加锁
         while(is_full())
         {
           pthread_cond_wait(&proCond_,&mutex_);//若队列满了,则进行等待,同时把锁释放,再次醒来时会重新获取锁
         }
         _q.push(in);
        unlockQueue();//生产完后解锁
        pthread_cond_signal(&conCond_);//消费者可能在等待,要去唤醒
    }

    T _pop()
    {
      lockQueue();//消费之前先加锁
      while(_q.empty())
      {
        pthread_cond_wait(&conCond_,&mutex_);//若队列为空,则进行等待,同时把锁释放,再次醒来时会重新获取锁
      }
      T out=_q.front();
      _q.pop();
      unlockQueue();//消费完后解锁
      pthread_cond_signal(&proCond_);//唤醒生产者
      return out;
    }
 private:
    void lockQueue()
    {
      pthread_mutex_lock(&mutex_);
    }
    void unlockQueue()
    {
      pthread_mutex_unlock(&mutex_);
    }
    bool is_full()
    {
      return size_==_q.size();
    }

    queue<T> _q;
    pthread_mutex_t mutex_;
    pthread_cond_t proCond_;
    pthread_cond_t conCond_;
    int size_;
};

模拟任务task.cpp代码:

#include <iostream>
#include <string>

class Task
{
public:
    Task(int one, int two, char op) : elemOne_(one), elemTwo_(two), operator_(op)
    {
    }
    int operator()()
    {
        return run();
    }
    int run()
    {
        int result = 0;
        switch (operator_)
        {
        case '+':
            result = elemOne_ + elemTwo_;
            break;
        case '-':
            result = elemOne_ - elemTwo_;
            break;
        case '*':
            result = elemOne_ * elemTwo_;
            break;
        case '/':
        {
            if (elemTwo_ == 0)
            {
                std::cout << "div zero, abort" << std::endl;
                result = -1;
            }
            else
            {
                result = elemOne_ / elemTwo_;
            }
        }

        break;
        case '%':
        {
            if (elemTwo_ == 0)
            {
                std::cout << "mod zero, abort" << std::endl;
                result = -1;
            }
            else
            {
                result = elemOne_ % elemTwo_;
            }
        }
        break;
        default:
            std::cout << "非法操作: " << operator_ << std::endl;
            break;
        }
        return result;
    }
    int get(int *e1, int *e2, char *op)
    {
        *e1 = elemOne_;
        *e2 = elemTwo_;
        *op = operator_;
    }
private:
    int elemOne_;
    int elemTwo_;
    char operator_;
};

 BlockQueue.cpp代码:

#include"BlockQueue.hpp"
#include"task.hpp"
#include <ctime>

const std::string ops = "+-*/%";
void *productor(void *args)
{
    BlockQueue<Task> *bqp = static_cast<BlockQueue<Task> *>(args);
    while (true)
    {
        //制作任务
        int one = rand() % 50;
        int two = rand() % 20;
        char op = ops[rand() % ops.size()];
        Task t(one, two, op);

        bqp->_push(t);//生产任务
        cout << "producter[" << pthread_self() << "] " << (unsigned long)time(nullptr) << " 生产了一个任务: " << one << op << two << "=?" << endl;
        sleep(1);
    }
}
void *consumer(void *args)
{
    BlockQueue<Task> *bqp = static_cast<BlockQueue<Task> *>(args);
    while (true)
    {
        Task t = bqp->_pop(); // 消费任

        int result = t();    //处理任务 
        int one, two;
        char op;
        t.get(&one, &two, &op);
        cout << "consumer[" << pthread_self() << "] " << (unsigned long)time(nullptr) << " 消费了一个任务: " << one << op << two << "=" << result << endl;
    }
}
int main()
{
    srand((unsigned long)time(nullptr));
    BlockQueue<Task> bq; //阻塞队列

    pthread_t tid1, tid2;
    pthread_create(&tid1, nullptr, consumer, &bq);
    pthread_create(&tid2, nullptr, productor, &bq);

    pthread_join(tid1, nullptr);
    pthread_join(tid2, nullptr);
    return 0;
}

结果:

 三.信号量

3.1原理

当我们仅用一个互斥锁对临界资源进行保护时,相当于我们将这块临界资源看作一个整体,同一时刻只允许一个执行流对这块临界资源进行访问。
但实际我们可以将这块临界资源再分割为多个区域,当多个执行流需要访问临界资源时,如果这些执行流访问的是临界资源的不同区域,那么我们可以让这些执行流同时访问临界资源的不同区域,此时不会出现数据不一致等问题。

概念:本质上就是一个计数器,去划分临界资源的数量。

每个执行流在进入临界区之前都要先申请信号量,申请成功就有了访问临界资源的权限,当操作完毕后再释放信号量。就是对信号量做加减操作。

信号量的PV操作

p操作:申请信号量为p操作,当申请成功,就获得了访问该临界资源的权限,同时,该临界资源的数量也减少了一份,所以计数器要减一。

v操作:释放信号量为v操作,释放信号量的本质就是归还临界资源中某块资源的使用权限,当释放成功时临界资源中资源的数目就应该加一。

补充:PV操作必须是原子性的。多个执行流访问临界资源也是竞争式的,因此信号量是会被多个执行流同时访问的,也就是说信号量本质也是临界资源。当信号量值为零时被申请,那么该执行流会在该信号量的等待队列当中进行等待,直到有信号量被释放时再被唤醒。

3.2信号量函数

初始化信号量函数:

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

参数解释:

  • sem:需要初始化的信号量。
  • pshared:传入0值表示线程间共享,传入非零值表示进程间共享。
  • value:信号量的初始值(计数器的初始值)。

销毁信号量函数:

int sem_destroy(sem_t *sem);

申请信号量函数(等待):

int sem_wait(sem_t *sem);

 申请成功后值减一

释放信号量函数(发布):

int sem_post(sem_t *sem);

 释放信号量后值加一

上面这些函数调用成功返回0,失败返回-1。

3.3信号量模拟互斥功能

当把信号量的值设置为1时,那么它说明临界资源的数量只有一分,信号量的作用基本等价于互斥锁。

class Sem{
public:
	Sem(int num)
	{
		sem_init(&_sem, 0, num);
	}
	~Sem()
	{
		sem_destroy(&_sem);
	}
	void P()
	{
		sem_wait(&_sem);
	}
	void V()
	{
		sem_post(&_sem);
	}
private:
	sem_t _sem;
};

Sem sem(1); //二元信号量
int tickets=1000;
void* getTickets(void* args)
{
  string s=(char*)args;
  while(true)
  {
    sem.P();
    if(tickets>0)
   {
     usleep(10000);
     cout<<s<<" "<<"抢到票了,"<<"票数还剩下:"<<--tickets<<endl;
     sem.V();
   }
   else
   {
     cout<<"票已经完了"<<" "<<s<<" "<<"退出了"<<endl;
     sem.V();
     break;
   }
   
 }
 return nullptr;
}
int main()
{
  pthread_t tid1;
  pthread_t tid2;
  pthread_t tid3;
  pthread_t tid4;
  
  pthread_create(&tid1,nullptr,getTickets,(void*)"pthread1");
  pthread_create(&tid2,nullptr,getTickets,(void*)"pthread2");
  pthread_create(&tid3,nullptr,getTickets,(void*)"pthread3");
  pthread_create(&tid4,nullptr,getTickets,(void*)"pthread4");

  pthread_detach(tid1);
  pthread_detach(tid2);
  pthread_detach(tid3);
  pthread_detach(tid4);

  while(true)
  {
    ;
  }
  return 0;
}

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

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

相关文章

【C语言】十六进制转换为十进制

目录 题目描述 补充知识&#xff1a; 算法分析 优化算法 写在最后 题目描述 输入一个十六进制数字串&#xff0c;将其转换成为对应的整数并输出转换结果&#xff0c;遇到非十六进制数字或字符串结束符&#xff08;\0&#xff09;结束转换。 注意&#xff1a; 输入的字符…

【Java多线程】初识线程及三种创建方式

➤ Java多线程编程【一文全解】 文章目录线程简介进程的创建> 继承 Thread 类> 实现 Runnable 接口> 实现 Callable 接口线程简介 普通的程序中&#xff0c;方法的调用是执行到方法的时候&#xff0c;程序跳转到方法体中进行&#xff0c;是按照顺序进行的&#xff0c;…

说说未来趋势 「元宇宙」是什么?

最近「元宇宙」概念大火&#xff0c;连星爷等各行各业的各路大佬都可以传出消息布局进入这一个领域&#xff0c;那么这是不是意味这IT信息化时代的下一个风口&#xff0c;就是元宇宙呢&#xff1f;按小郭说呀&#xff0c;这目前来看&#xff0c;这个趋势是必然的&#xff0c;就…

Spirng 痛苦源码学习(一)——总起spring(一)

文章目录前言一、总览Spring的bean1&#xff09;bean的过程【先了解具体的生命周期后面弄】2&#xff09;hello spring 简单bean操作二、总览AOP- 1、test coding- 2、- debug- 3、- 总结debug三、总览事务- 1、- test coding- 2、 debugging- 3、 事务失效- 4、事务总结前言 …

cpu设计和实现(流水线暂停)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 前面我们说过&#xff0c;数字电路里面流水线的引入&#xff0c;主要是为了提高数据的处理效率。那么&#xff0c;鉴于此&#xff0c;为什么又要对…

ssm宠物商城管理系统源码

在 Internet飞速开展的今天&#xff0c;互联网成为人们快速获取、发布和传 递信息的重要渠道&#xff0c;它在人们学习、工作、生活等各个方面发挥着重要的作用。 因此建立在 Internet应用上的地位显而易见&#xff0c;它已成为政府、企事业单位信息化 建立中的重要组成局部&am…

[附源码]SSM计算机毕业设计网上书店管理系统JAVA

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

【kafka】十五、kafka消费者API

kafka消费者API Consumer消费数据时的可靠性是很容易保证的&#xff0c;因为数据在kafka中是持久化的&#xff0c;故不用担心数据丢失的问题。 由于consumer在消费过程中可能会出现断电宕机的等故障&#xff0c;consumer恢复后&#xff0c;需要从故障前的位置继续消费&#xf…

visual studio 2019 + Qt 开发,使用visual leak detector检测内存泄漏

选择了在vs2019上开发Qt, 遇到了内存泄露问题。还好vs上有方便的visual leak detector&#xff08;vld&#xff09;检测工具。 虽然官网上只支持到vs2015, 但vs2019上也能用。 具体参考这位博主的文章&#xff1a;https://blog.csdn.net/qq_22108657/article/details/1208843…

Redis数据库安装(Windows)

目录 一、下载Windows安装包 二、启动Redis 1.在终端中启动 2.使用start.bat文件启动 3.添加服务启动 三、安装Redis可视化管理工具 1.安装Redis图形客户端 2.连接数据库 一、下载Windows安装包 下载地址&#xff1a;Releases tporadowski/redis GitHub 选择下载相…

单链表的递归详解 (leetcode习题+ C++实现)

文章目录合并两个有序链表翻转链表链表中移除节点合并两个有序链表 传送门&#xff1a; https://leetcode.cn/problems/merge-two-sorted-lists/description/ 题目要求&#xff1b; 给你两个有序的链表&#xff0c;将这两个链表按照从小到大的关系&#xff0c;合并两个链表为…

Mybatis快速入门

Mybatis安装与配置 Mybatis概述 Mybatis本质上是一个别人写好的框架&#xff0c;用于简化 JDBC 开发&#xff0c;通过Mybatis框架&#xff0c;可以极大的降低JDBC的开发难度。 官方文档&#xff1a;https://mybatis.org/mybatis-3/zh/index.html Mybatis快速入门 需求&…

MySQL进阶实战10,MySQL全文索引

一、全文索引 全文索引的目的是 通过关键字的匹配进行查询过滤&#xff0c;基于相似度的查询&#xff0c;而不是精确查询。 全文索引利用分词技术分析出文字中某关键字的频率和重要性&#xff0c;并按照一定的算法智能的筛选出我们想要的结果。 全文索引一般用于字符串中某关…

tomcat服务器安装及配置教程(保姆级教程)

Tomcat安装教程 &#xff08;以tomcat-9.0.62为例&#xff1a;&#xff09; 1.下载安装包 可以从官网下载安装包&#xff1a; &#xff08;1&#xff09;从官网下载 输入网址进入官网 选择版本10&#xff0c;版本9&#xff0c;或者版本8&#xff0c;都可以&#xff0c;这里…

掘金热榜首推!阿里内部都在用的Java后端面试笔记,囊括99%的主流技术

纵观今年的技术招聘市场&#xff0c; Java依旧是当仁不让的霸主 &#xff01;即便遭受 Go等新兴语言不断冲击&#xff0c;依旧岿然不动。究其原因&#xff1a; Java有着极其成熟的生态&#xff0c;这个不用我多说&#xff1b;Java在 运维、可观测性、可监 控性方面都有着非常优…

Spring Boot JPA 本机查询示例

在本教程中&#xff0c;您将了解如何在 Spring 引导中使用 Spring Data JPA 本机查询示例&#xff08;带参数&#xff09;。我将向您展示&#xff1a; 将 Spring JPA 本机查询与Query注释一起使用的方法如何在 Spring 引导中执行 SQL 查询具有 WHERE 条件的 JPA 选择查询示例 …

动态SQL

动态SQL 可以根据具体的参数条件&#xff0c;来对SQL语句进行动态拼接。比如在以前的开发中&#xff0c;由于不确定查询参数是否存在&#xff0c;许多人会使用类似于where 1 1 来作为前缀&#xff0c;然后后面用AND 拼接要查询的参数&#xff0c;这样&#xff0c;就算要查询的…

MongoShake数据灾备与迁移

安装部署 解压 建议部署在离目标端近的地方&#xff0c;比如部署再目标端本地 tar -zxvf mongo-shake-v2.8.1.tgz配置 同构环境下主要参数 启动 执行下述命令启动同步任务&#xff0c;并打印日志信息&#xff0c;-verbose 0表示将日志打印到文件&#xff0c;在后台运行 …

【Linux从入门到放弃】Linux基本指令大全

&#x1f9d1;‍&#x1f4bb;作者&#xff1a; 情话0.0 &#x1f4dd;专栏&#xff1a;《Linux从入门到放弃》 &#x1f466;个人简介&#xff1a;一名双非编程菜鸟&#xff0c;在这里分享自己的编程学习笔记&#xff0c;欢迎大家的指正与点赞&#xff0c;谢谢&#xff01; L…

黑苹果之微星(MSI)主板BIOS详细设置篇

很多童鞋安装黑苹果的时候会卡住&#xff0c;大部分原因是cfg lock 没有关闭&#xff0c;以及USB端口或SATA模式设置错误。 为了避免这些安装阶段报错的情况发生&#xff0c;今天给大家分享一下超详细的BIOS防踩坑设置指南--微星&#xff08;MSI&#xff09;主板BIOS篇&#xf…