Linux入门之多线程|线程的同步|生产消费模型

news2024/9/23 20:21:50

文章目录

一、多线程的同步

1.概念

2.条件变量

2.1条件变量概念

2.2条件变量接口

1.条件变量初始化

2.等待条件满足

3.唤醒等待

3.销毁条件变量

2.3条件变量demo

二、生产消费模型

1.生产消费模型

2.基于BlockQueue的生产者消费者模型

3.基于C++用条件变量和互斥锁实现一个生产消费模型

4.信号量

1.信号量概念

2.信号量接口

1.初始化信号量

2.等待信号量(P操作 --)

3.发布信号量(V操作 ++)

4.销毁信号量

5.环形生产者消费者模型



当一个线程互斥地访问某个变量时,它发现可能再其他线程改变状态之前,它被挂起。

例如一个线程访问队列,发现队列为空,它只能等待。直到其他线程将一个节点加入到队列中,这种情况就需要用到条件变量。

一、多线程的同步

1.概念

在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源从而有效避免饥饿问题,叫做同步。

2.条件变量

2.1条件变量概念

条件变量是线程同步的一种手段,如果只有一个线程,条件不满足,一直等待下去都不会满足,所以必须要有一个线程通过某些操作,改变共享变量,使得原来的不满足条件变的满足,并且友好通知等待在条件变量上的线程。

条件变量不会无缘无故满足,必然牵扯到共享数据的变化,所以一定需要用锁来保护,没有锁就无法安全的获取和修改共享数据

2.2条件变量接口

1.条件变量初始化

int pthread_cond_init(pthread_cond_t * restrict cond,const pthread_condattr_t * restrict attr);

参数:cond 要初始化的条件变量
      attr:NULL

2.等待条件满足

int pthread_cond_wait(pthread_cond_t * restrict cond,pthread_mutex_t * restrict mutex);

参数:
    cond: 要在这个条件变量上等待
    mutex:互斥量,等待的时候要释放掉这个锁

3.唤醒等待

int pthread_cond_broadcast(pthread_cond_t * cond);
int pthread_cond_signal(pthread_cond_t * cond);

3.销毁条件变量

int pthread_cond_destroy(pthread_cond_t * cond);

2.3条件变量demo

#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<cstdio>
#include<string>
using namespace std;



const int num = 5;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;



void *active(void *args)
{
    string name = static_cast<const char*>(args);
    while(true)
    {
        pthread_mutex_lock(&mutex);
        pthread_cond_wait(&cond,&mutex);// 在调用的时候,会自动释放锁
        cout<<name<<" activing..."<<endl;
        pthread_mutex_unlock(&mutex);

    }

}
int main()
{
    pthread_t tids[num];
    for(int i = 0; i<num;i++)
    {
        char * name = new char[32];
        snprintf(name,32,"thread-%d",i+1);

        pthread_create(tids+i,nullptr,active,name);
    }


    sleep(3);

    while(true)
    {
        cout<<"main thread wakeup thread"<<endl;
        pthread_cond_signal(&cond);
        sleep(1);
    }


    for(int i = 0; i<num;i++)
    {
        pthread_join(tids[i],nullptr);
    }

}

二、生产消费模型

1.生产消费模型

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

2.基于BlockQueue的生产者消费者模型

在多线程编程中阻塞队列 (Blocking Queue) 是一种常用于实现生产者和消费者模型的数据结构。其与普通的队列区别在于,当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素;当队列满时,往队列里存放元 素的操作也会被阻塞,直到有元素被从队列中取出(以上的操作都是基于不同的线程来说的,线程在对阻塞队列进程 操作时会被阻塞。

3.基于C++用条件变量和互斥锁实现一个生产消费模型

需求:使用条件变量和互斥锁实现一个生产消费模型。生产消费模型是一个队列,如上图,这里使用stl中的队列,先实现单生产单消费,一个线程负责生产,一个线程负责消费。这两个线程需要访问同一个队列,所以需要一把锁。在线程挂起的时候,还需要有信号告诉线程现在条件满足了,所以要使用两个条件变量,分别通知生产和消费线程转为就绪态。

实现:将这个模型封装成一个类,两个线程去访问的时候,队列非满,就可以生产,往队列里push数据,队列中数据非空,就可以消费,从队列里pop数据。所以需要两个接口push和pop,调试成功后,最后使用多生产多消费实现。实现代码如下:

//blockqueue.hpp 声明,方法,定义在一个文件中

const int gcap = 5;
template<class T>
class blockQueue
{

public:
    //构造
    blockQueue(const int cap = gcap)
    :_cap(cap),
    {
        pthread_mutex_init(&_mutex,nullptr);
        pthread_cond_init(&_consumerCond,nullptr);
        pthread_cond_init(&_productorCond,nullptr);
    }

    bool isFull() {return _q.size() == _cap;}
    bool isEmpty() { return _q.empty()};

    //将数据塞进队列 生产
    void push(const T & in)
    {
      pthread_mutex_lock(&_mutex);
      //注意这里不要用if,可能会误唤醒
        while(isFull())
        {
            //在当前的条件下休眠,就注定了要释放锁,让别的线程去竞争锁
            //休眠,就是被os切走了,醒来之后又要重新申请锁
            pthread_cond_wait(&_productorCond,&_mutex);  
        }
        
        //如果没满,就让他继续生产
        _q.push(in);
        
        //生产之后,让消费者来消费,唤醒消费的线程 再释放自己手中的锁
        pthread_cond_signal(&_consumerCond);
        pthread_mutex_unlock(&_mutex);
    }

    //队列非空 消费  
      void pop()
    {
        pthread_mutex_lock(&_mutex);
        //判断队列是否为空
        while(isEmpty())
        {
            //空的话,在当前条件下休眠
            pthread_cond_wait(&_consumerCond,&_mutex);
        }

        //非空 开始消费 并且唤醒生产者 可以生产了
        pthread_cond_signal(&_productorCond);
        pthread_mutex_unlock(&_mutex);
    }

    //析构
    ~blockQueue()
    {
        //释放锁和两个信号量 队列是一个临时变量可以不用在这里释放
        pthread_mutex_destroy(&mutex);
        pthread_cond_destroy(&_consumerCond);
        pthread_cond_destroy(&_productorCond);
    }

private:
    std::queue<T> _q;
    int _cap; //队列中的容量
    pthread_mutex_t _mutex;
    pthread_cond_t _consumerCond; //消费者对应的条件变量,如果队列中数据为空,wait
    pthread_cond_t _productorCond; //生产者对应的条件变量,如果队列中数据为满,wait
};

4.信号量

1.信号量概念

        POSIX信号量和System V 信号量作用相同,都是用于同步操作,达到无冲突访问共享资源的目的。但是POSIX可以用于线程间同步。信号量本质就是用来描述临界资源中的数量

        sem = 1,就只有0/1两种状态,就是互斥锁。

        多元信号量:每个线程,在访问对应资源时,先申请信号量。申请成功,就表示现在可以时候该资源。申请失败,就目前无法访问。

2.信号量接口

#include<semaphore.h>

1.初始化信号量

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

参数:pshared 0 表示线程间共享,非0 表示进程间共享
     value:信号量初始值

2.等待信号量(P操作 --)

int sem_wait(sem_t * sem);

3.发布信号量(V操作 ++)

int sem_post(sem_t * sem);

4.销毁信号量

int sem_destroy(sem_t * sem);

5.环形生产者消费者模型 

       环形队列采用数组模拟,用%运算来模拟环状特性。在为空/满的时候要保证游戏规则,在非空非满的时候保证并发。

环形结构起始状态和结束状态都是一样的,不容易判断是空还是满,所以通过加计数器或者标记位来判断。另外也可以预留一个空的位置,作为满的状态。

但是我们现在有信号量这个计数器,就可以进行多线程间的同步过程。

生产者关心这个空间是否满了,消费者关心是否有数据。环形队列只要访问不同的区域,生产和消费行为可以同时进行。

需求:生产消费模型是一个队列,使用数组模拟,同时需要两个线程,生产线程和消费线程。要维护3种关系:生产者和生产者,消费者和消费者,生产者和消费者之间的关系。其中生产者和生产者,需要互斥。消费者和消费者同样互斥。生产者和消费者,需要先生产再消费,所以需要同步。同时,访问同一个队列(共享资源)需要互斥关系。

实现:将队列封装成类,用数组模拟实现。类需要暴露的接口就是pushpop,即实现p操作和v操作。同时要知道这个队列的大小定义两个信号量,一个是消费者关心的,一个是生产者关心。申请信号量成功之后,也要知道生产和消费此刻对应队列中的位置,就是具体维护哪个区域。即两个下标。申请自己关心的资源,互相V对方的资源

static const int N = 5;

template<class T>
class RingQueue
{
    private:
        void P(sem_t &s)
        {
            sem_wait(&s);
        }

        void V(sem_t &s)
        {
            sem_post(&s);
        }

        
    public:
        //构造
           RingQueue(int num = N)
            :_ring(num),_cap(num)
        {

            sem_init(&_data_sem,0,0);        
            sem_init(&_space_sem,0,num);
            
            //刚开始都为0
            _c_step = _p_step = 0;
        }

        void push(const T &in)
        {

           //申请
           P(_space_sem);
           _ring[_p_step++] = in;
           _p_step %= _cap;
           V(_data_sem);
        }
        
        void pop(T * out)
        {

            P(_data_sem);
            *out = _ring[_c_step++];
            _c_step &= _cap;
            V(_spcae_sem);
        }

        ~RingQueue()
        {
            sem_destroy(&_data_sem);
            sem_destroy(&_space_sem);
        }
    private:
        std::vector<T> _ring;
        int _cap;
        sem_t _data_sem;
        sem_t _space_sem;
        int _c_step;
        int _p_step;
};

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

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

相关文章

RecyclerView+Flexbox实现流式布局

之前使用 FlexboxLayout 实现流式布局&#xff0c;但是选中和反选效果不好实现&#xff0c;就改用RecyclerViewFlexboxLayoutManager 实现流式布局&#xff1a; 说明&#xff1a;如果是直接展示标签&#xff0c;没有其他选中效果时&#xff0c;建议直接使用 FlexboxLayout实现…

Scrum认证高级Scrum Master (A-CSM) 认证培训课程

课程简介 高级ScrumMaster (Advanced Certified ScrumMaster, A-CSM) 认证课程是国际Scrum联盟推出的进阶级Scrum认证课程&#xff0c;是Scrum Master通往专业级敏捷教练必经的学习路径。 在ScrumMaster&#xff08;CSM&#xff09;认证课程中&#xff0c;您学习到了Scrum的价…

Redis核心数据结构与高性能原理

Redis的单线程和高性能 Redis是单线程吗&#xff1f; Redis 的单线程主要是指 Redis 的网络 IO 和键值对读写是由一个线程来完成的&#xff0c;这也是 Redis 对外提供键值存储服务的主要流程。但 Redis 的其他功能&#xff0c;比如持久化、异步删除、集群数据同步等&#xff…

Mybatis复杂查询及动态SQL

文章目录 一. 较复杂的查询操作1. 参数占位符#{}和${}2. SQL注入3. like查询4. resultType与resultMap5. 多表查询5.1. 一对一表映射5.2. 一对多表映射 二. 动态SQL1. if标签2. trim标签3. where标签4. set标签5. foreach标签 本篇中使用的数据表即基础映射类都是基于上一篇博客…

什么是SpringMVC以及SpringMVC框架的优点

它是基于MVC开发模式的框架,用来优化控制器.它是Spring家族的一员.它也具备IOC和AOP. 什么是MVC? 它是一种开发模式,它是模型视图控制器的简称.所有的web应用都是基于MVC开发. M:模型层,包含实体类,业务逻辑层,数据访问层 模型 模型(Model)&#xff1a;就是业务流程/状态…

每日刷题-2

目录 一、选择题 二、编程题 1、倒置字符串 2、排序子序列 3、字符串中找出连续最长的数字串 4、数组中出现次数超过一半的数字 一、选择题 1、 题目解析&#xff1a; 二维数组初始化的一般形式是&#xff1a; 数据类型 数组名[常量表达式1][常量表达式2] {初始化数据}; 其…

使用SpringCloud Eureka 搭建EurekaServer 集群- 实现负载均衡故障容错【上】

&#x1f600;前言 本篇博文是关于使用SpringCloud Eureka 搭建EurekaServer 集群- 实现负载均衡&故障容错&#xff0c;希望你能够喜欢 &#x1f3e0;个人主页&#xff1a;晨犀主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是晨犀&#xff0c;希望我的文章可…

使用共享 MVI 架构实现高效的 Kotlin Multiplatform Mobile (KMM) 开发

使用共享 MVI 架构实现高效的 Kotlin Multiplatform Mobile (KMM) 开发 文章中探讨了 Google 提供的应用架构指南在多平台上的实现。通过共享视图模型&#xff08;View Models&#xff09;和共享 UI 状态&#xff08;UI States&#xff09;&#xff0c;我们可以专注于在原生端…

RHCSA-VMware Workstation Pro-Linux基础配置命令

1.代码命令 1.查看本机IP地址&#xff1a; ip addr 或者 ip a [foxbogon ~]$ ip addre [foxbogon ~]$ ip a 1&#xff1a;<Loopback,U,LOWER-UP> 为环回2网卡 2: ens160: <BROADCAST,MULTICAST,UP,LOWER_UP>为虚拟机自身网卡 2.测试网络联通性&#xff1a; [f…

【0907作业】写一个shell脚本,将以下内容放到脚本中

在家目录下创建目录文件&#xff0c;dir在dir下创建dir1和dir2把当前目录下的所有文件拷贝到dir1中&#xff0c;把当前目录下的所有脚本文件拷贝到dir2中把dir2打包并压缩为dir2.tar.xz再把dir2.tar.xz移动到dir1中解压dir1中的压缩包使用tree工具&#xff0c;查看dir下的文件 …

vue3:5、组合式API-reactive和ref函数

<script setup> /* reactive接收一个对象类型的数据&#xff0c;返回一个响应式的对象 *//*** ref:接收简单类型或复杂类型&#xff0c;返回一个响应式对象* 本质&#xff1a;是在原有传入数据的基础上&#xff0c;外层报了一层对象&#xff0c;包成了复杂类型* 底层&…

宇凡微YE09合封芯片,集成高性能32位mcu和2.4G芯片

合封芯片是指将主控芯片和外部器件合并封装的芯片&#xff0c;能大幅降低开发成本、采购成本、减少pcb面积等等。宇凡微YE09合封芯片&#xff0c;将技术领域推向新的高度。这款高度创新性的芯片融合了32位MCU和2.4G芯片&#xff0c;为各种应用场景提供卓越的功能和性能。 32位M…

CSDN: ABTest流量分层分桶机制

在互联网行业&#xff0c;无论是构建搜索推荐系统&#xff0c;还是智能营销等场景&#xff0c;都是围绕用户进行不同的实验&#xff0c;从各项指标上观察用户对不同交互、流程、策略、算法等反馈&#xff0c;进而对产品进行迭代改进。 本文的goal&#xff1a;在进行了模型的线下…

STM32 FreeRTOS 内存问题

1. STM32L151C8T6 内存&#xff0c;64Kb 的Flash&#xff08;代码就是烧录在这里面的&#xff09;&#xff0c;16Kb 的RAM&#xff0c;程序跑起来之后的内存&#xff0c;相当于我们高考时发的草稿纸&#xff0c;直接影响程序的运行速度&#xff0c;可以用STM32 CubeMx 软件直接…

JMeter压力测试 5分钟让你学会如何并发压测接口

文章目录 地址下载启动 使用 地址 JMeter官网下载&#xff1a;https://jmeter.apache.org/download_jmeter.cgi 下载 最新款的jmeter需要java8的支持&#xff0c;请自行安装jdk8或以上的版本 根据系统不同系统下载zip或者是tgz格式的压缩包&#xff0c;并解压&#xff0c;博…

Google 在 2023 开发者大会上的 AI 革命

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页 ——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

对线程池设置做压测

线程池代码 Configuration public class ThreadPoolConfig {// 核心线程池大小private int corePoolSize 24;// 最大可创建的线程数private int maxPoolSize 25;// 队列最大长度private int queueCapacity 100;// 线程池维护线程所允许的空闲时间private int keepAliveSeco…

qt作业day4

//clock_exercise.cpp#include "clock_timer.h" #include "ui_clock_timer.h"//时间事件处理函数 void Clock_Timer::timerEvent(QTimerEvent *event) {if(event->timerId() time_id){sys_tm QDateTime :: currentDateTime(); // int year sy…

docker-compose安装redis

基于docker-compose快速安装redis 目录 一、目录结构 1、docker-compose.yml 2、redis.conf 二、连接使用 一、目录结构 1、docker-compose.yml version: 3 services:redis:image: registry.cn-hangzhou.aliyuncs.com/zhengqing/redis:6.0.8 # 镜像red…

高通DSP架构和HVX指令介绍

1. Qualcomm Snapdragon™处理器 Qualcomm Snapdragon™是高通的移动平台处理器,是一种系统级芯片(SoC),包含了CPU、GPU、DSP、调制解调器、无线电、摄像头处理器、安全处理器等多种功能。Snapdragon处理器广泛应用于智能手机、平板电脑、智能手表、智能音箱等移动设备中。…