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

文章目录

一、多线程的同步

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; //消费者对应的条件变量,如果队列中数据为空,waitpthread_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.hqwc.cn/news/101849.html

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

相关文章

c语言数组的用法

c语言数组的用法如下&#xff1a; 一维数组的定义方式 在C语言中使用数组必须先进行定义。一维数组的定义方式为&#xff1a; 类型说明符 数组名 [常量表达式]; 其中&#xff0c;类型说明符是任一种基本数据类型或构造数据类型。数组名是用户定义的数组标识符。方括号中的常量表…

9月7日扒面经

redis缓存用在哪里&#xff0c;用本地缓存行不行? 数据库查询缓存&#xff0c;减小数据源压力&#xff0c;提高响应速度 页面缓存&#xff1a;将页面的渲染结果缓存在Redis中&#xff0c;以减少页面生成的时间和服务器负载。 频繁计算结果缓存&#xff1a;将频繁计算的结果…

stable diffusion实践操作-随机种子seed

系列文章目录 stable diffusion实践操作 文章目录 系列文章目录前言一、seed是什么&#xff1f;二、使用步骤1.多批次随机生成多张图片2.提取图片seed3. 根据seed 再次培养4 seed使用4.1 复原别人图4.1 轻微修改4.2 固定某个人物-修改背景 三、差异随机种子1. webUI位置2. 什么…

maven打包时显示无效jdk版本

1、配置当前项目所需的Jdk版本 2、与当前项目指定的jdk版本相同 3、与当前项目指定的jdk版本相同 4、与当前项目指定的jdk版本相同 5、指定主项目启动时的vm配置与当前项目所需版本相同

Linux系统离线安装RabbitMQ

安装rabbitmq 1、下载安装包 首先进入官网进行安装包的下载&#xff0c;在下载时一定要注意erlong版本和rabbitmq-server版本匹配 rabbitmq版本对应关系&#xff1a;传送门 Erlong下载地址:传送门 rabbitmq-server下载地址:传送门 socat 不同版本 centos7:传送门 cent…

nas汇编程序的调试排错方法

nas汇编程序的调试排错方法&#xff1a; 1、查找是哪一步错了 2、查看对应的*.lst文件&#xff0c;本例中是"asmhead.lst" 3、根据*.lst文件的[ERROR #002]提示查看源码&#xff0c;改错。 4、重新运行编译&#xff0c;OK 1、查找是哪一步错了&#xff1a; nask.ex…

QT之形态学操作

形态学操作包含以下操作&#xff1a; 腐蚀 (Erosion)膨胀 (Dilation)开运算 (Opening)闭运算 (Closing)形态梯度 (Morphological Gradient)顶帽 (Top Hat)黑帽(Black Hat) 其中腐蚀和膨胀操作是最基本的操作&#xff0c;其他操作由这两个操作变换而来。 腐蚀 用一个结构元素…

Spring-TX 事务

目录 一、事务的种类 二、Spring事务管理器 三、事务注解使用 四、事务注解属性 一、事务的种类 1.编程式事务 所谓编程式事务就是用代码手写事务&#xff0c;包含了事务的开始&#xff0c;具体事务&#xff0c;事务的提交和事务的回滚。在这期间就会产生一些冗余问题&am…

typeScript学习笔记(一)

学习资源来自&#xff1a; 类与接口 TypeScript 入门教程 (xcatliu.com) 一.TypeScript的安装和运行 1.安装TypeScript 通过npm&#xff08;Node.js包管理器&#xff09;安装Visual Studio的TypeScript插件:(Visual Studio 2017和Visual Studio 2015 Update 3默认包含了Typ…

关于MyBatisPlus框架下出现xml里面定义的方法无法被正确识别以及提示调用mysql存储过程时参数无效的问题

第一个问题&#xff1a;xml里面明明定义了方法A&#xff0c;但是通过IService接口调用A的时候&#xff0c;总提示无法将接口中定义的函数绑定到xml中的同名方法中&#xff08;“Invalid bound statement (not found): com.aircas.sqlservice.mapper.SysTempIndexMapper.getRemo…

C++——智能指针

智能指针 文章目录 智能指针内存泄漏智能指针解决内存泄漏问题智能指针的使用及原理RAII智能指针对象的拷贝问题 C中的智能指针auto_ptrunique_ptrshared_ptrweak_ptr定制包装器C11和boost中智能指针的关系 内存泄漏 什么是内存泄漏&#xff1a;内存泄漏指因为疏忽或错误造成程…

Java多线程篇(1)——深入分析synchronized

文章目录 synchronized原理概述锁升级 初始状态偏向锁偏向锁获取/重入偏向锁的撤销/重偏向和升级批量重偏向和批量偏向撤销偏向锁的释放 轻量级锁轻量级锁获取/重入轻量级锁膨胀轻量级锁释放 重量级锁重量级锁获取/重入重量级锁释放重量级锁的降级 其他锁粗化、锁消除调用hashc…