Linux——多线程(四)

前言

 这是之前基于阻塞队列的生产消费模型中Enqueue的代码

    void Enqueue(const T &in) // 生产者用的接口{pthread_mutex_lock(&_mutex);while(IsFull())//判断队列是否已经满了{pthread_cond_wait(&_product_cond, &_mutex); //满的时候就在此情况下等待// 1. pthread_cond_wait调用是: a. 让调用进程等待 b. 自动释放曾经持有的_mutex锁}// 进行生产_bq.push(in);// 通知消费者来消费pthread_cond_signal(&_consumer_cond);pthread_mutex_unlock(&_mutex);}

缺点:

当一个线程往阻塞队列中插入时,必须要满足一个条件"临界资源还没满",否则就需要放到条件变量的等待队列中去。

而判断临界资源是否为满需要先申请锁(检测临界资源的本质也是在访问临界资源),然后再进入临界区访问临界资源,才能判断临界资源是否为满。

那么只要我们对临界资源整体加锁,就默认会对这个临界资源整体使用(吗?)。实际上可能是:一份临界资源被划分为多个不同的区域,而且运行多个线程同时访问不同的区域。

 在访问临界资源之前,我们无法知道临界资源的情况。

多个线程不能同时访问临界资源的不同区域。

1.信号量

1.1信号量的概念 

我们之前在学进程间通信时,简单介绍过信号量。 

信号量:信号量本质其实是一计数器,这一计数器的作用是用来描述临界资源中资源数量的多少

申请信号量的本质其实就是:对临界资源中特定的小块资源预定机制。(资源不一定被我持有,才是我的,只要我预定了,在未来的某个时间,就是我的)

 信号量也是一种互斥量,只要申请到信号量的线程,在未来一定能够拥有一份临界资源。

假如要让多个线程同时去访问一块划分为n个区域的临界资源:

创建一个信号量,值为n

每来一个访问临界资源的线程都要先去申请信号量(信号量的值,n--),申请后才能访问

当n被减到0时说明临界资源中各个区域都有线程在访问资源,其它想要访问临界资源的线程就得阻塞等待,等这n个区域中的某个线程访问完将这个区域空出来才行(信号量的值,n++)

信号量解决了上面提到的问题:

线程不用访问临界资源就能知道资源的使用情况 (信号量申请成功就一定有资源可以使用,申请失败则说明条件不满足,只能阻塞等待)

注意:所有线程都得能看到信号量,信号量是一个公共资源,涉及到线程安全问题

信号量的基本操作就是对信号量进行++或--,而这两个操作时原子的

P操作:信号量--,就是在申请资源(此操作必须时原子的)

V操作:信号量++,返回资源(此操作也需是原子的)

1.2信号量的接口

信号量的使用需要引头文件:semaphore.h;还需要链接原生线程库-pthread 

sem_t sem;//创建信号量

初始化信号量

man sem_init

 参数:

sem:信号量指针

pshared:0表示线程间共享,非0表示进程间共享。(一般情况下为0)

value:信号量初始值,也就是计数器的值

返回值:类型int,成功返回0,失败返回-1,并将 errno 设置为指示错误

申请信号量,P操作,计数器--

man sem_wait

参数

sem:信号量指针

返回值:成功返回0,失败返回-1,设置errno

发布信号量,V操作,计数器++

man sem_post

参数

sem:信号量指针

返回值:成功返回0,失败返回-1,设置errno

信号量销毁

man sem_destroy

参数

sem:信号量指针

返回值:成功返回0,失败返回-1,设置errno

 2.基于环形队列的生产者消费者模型

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

2.1分析

环形队列

 这里的环形队列用数组来模拟,取模来模拟其环状特性

当环形队列为空时,头尾都指向同一个位置;当环形队列为满时,头尾也指向同一个位置。这样不好判断为空或为满,可以通过加计数器或者标记位来判断满或空,也可以预留一个空位,作为满状态

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

单生产和单消费两线程在访问环形队列时,生产者负责向环形队列中生产数据,消费者负责从环形队列中消费数据。

那么生产者和消费者什么时候会访问同一个位置呢?当环形队列为空/为满的时候

那么如果环形队列一定不为空&&一定不为满时,生产者、消费者的下标指向不是同一个位置

生产和消费的动作可以真正并发吗?是的 

环形队列的生产者消费者模型还需要两个必要条件:

1.生产者不能把消费者超过一个圈以上(当环形队列满了后,生产者继续生产,那么生产者生产的数据会覆盖消费者还未消费的,消费者就无法消费被覆盖的数据了)

2.消费者不能超过生产者(生产者还没生产,消费者无法消费。当消费者超过生产者时,消费者访问的区域无数据)

对于生产者而言,它最关心的是空间

空间资源可以定义一个信号量,用来统计空闲空间的个数

对于消费者而言,它最关心的是数据

数据资源也可以用一个信号量来统计数据个数

所以生产者每次访问临界资源之前,需要先申请空间资源的信号量,申请到才可以进行生产,不然就得老实的阻塞等待

消费者也一样,访问临界资源之前,要先申请数据资源的信号量,申请成功才能够去消费数据,不然还是阻塞等待

空间资源信号量的申请(P)由生产者进行,归还(V)由消费者进行

数据资源信号量的申请(P)由消费者进行,归还(V)由生产者进行

伪代码

生产者

P(room);//申请空间资源

//信号量申请成功,继续向下运行;失败则阻塞

ringbuffer[p_index] = x;

p_index++;

p_index%=10;

V(data);

消费者

P(data);//申请数据资源

//信号量申请成功——数据资源一定存在

out = ringbuffer[c_index];

c_index++;

c_index%=10;\

V(room);

2.2代码

ringqueue.hpp

#include <iostream>
#include <string>
#include <vector>
#include <semaphore.h>
#include <pthread.h>template<typename T>
class RingQueue
{
private:void P(sem_t &sem)//申请信号量P操作{sem_wait(&sem);}void V(sem_t &sem)//发布信号量V操作{sem_post(&sem);}void Lock(pthread_mutex_t &mutex)//加锁{pthread_mutex_lock(&mutex);}void UnLock(pthread_mutex_t &mutex)//解锁{pthread_mutex_unlock(&mutex);}
public:RingQueue(int cap):_rq(cap),_cap(cap),_productor_step(0),_consumer_step(0){sem_init(&_room_sem,0,_cap);sem_init(&_data_sem,0,0);pthread_mutex_init(&_productor_mutex,nullptr);pthread_mutex_init(&_consumer_mutex,nullptr);}void Enqueue(const T&in){P(_room_sem);Lock(_productor_mutex);//开始生产_rq[_productor_step] = in;_productor_step %= _cap;//环形队列UnLock(_productor_mutex);V(_data_sem);}void Pop(T *out){P(_data_sem);Lock(_consumer_mutex);//消费*out = _rq[_consumer_step];_consumer_step++;_consumer_step%=_cap;UnLock(_consumer_mutex);V(_room_sem);}~RingQueue(){sem_destroy(&_room_sem);sem_destroy(&_data_sem);pthread_mutex_destroy(&_productor_mutex);pthread_mutex_destroy(&_consumer_mutex);}
private:std::vector<T> _rq;int _cap;int _productor_step;//生产者步数int _consumer_step;//消费者步数//定义信号量sem_t _room_sem;//空间信号量,生产者关心sem_t _data_sem;//数据信号量,消费者关心//锁pthread_mutex_t _productor_mutex;pthread_mutex_t _consumer_mutex;
};

thread.hpp

#include<iostream>
#include<signal.h>
#include<unistd.h>
#include<functional>
#include<pthread.h>namespace Thread_Module
{template <typename T>using func_t = std::function<void(T&)>;// typedef std::function<void(const T&)> func_t;template <typename T>class Thread{public:void Excute(){_func(_data);}public:Thread(func_t<T> func,T &data,const std::string &threadname = "none"):_threadname(threadname),_func(func),_data(data){}static void* threadrun(void *args)//线程函数{Thread<T> *self = static_cast <Thread<T>*>(args);self->Excute();return nullptr;}bool Start()//线程启动!{int n = pthread_create(&_tid,nullptr,threadrun,this);if(!n)//返回0说明创建成功{_stop = false;//说明线程正常运行return true;}else{return false;}}void Stop(){_stop = true;}void Detach()//线程分离{if(!_stop){pthread_detach(_tid);}}void Join()//线程等待{if(!_stop){pthread_join(_tid,nullptr);}}std::string threadname()//返回线程名字{return _threadname;}~Thread(){}private:pthread_t _tid;//线程tidstd::string  _threadname;//线程名T &_data;//数据func_t<T> _func;//线程函数bool _stop; //判断线程是否停止 为true(1)停止,为false(0)正常运行};
}

main.cc

#include "ringqueue.hpp"
#include "Thread.hpp"
#include<string>
#include<vector>
#include<unistd.h>
#include<functional>
#include<pthread.h>using namespace Thread_Module;void* consumer(RingQueue<int> &rq)
{while(true){int data;rq.Pop(&data);std::cout<<"消费一个数据:"<<data<<std::endl;sleep(1);}  
}void* productor(RingQueue<int> &rq)
{   int a = 1;while(true){rq.Enqueue(a);std::cout<<"生产一个数据 :"<<a<<std::endl;a++;}
}void Comm(std::vector<Thread<RingQueue<int>>> *threads,int num,RingQueue<int> &rq,func_t<RingQueue<int>> func)
{for(int i=0;i<num;i++){std::string name = "thread-"+std::to_string(i+1);threads->emplace_back(func,rq,name);}
}void ProductorStart(std::vector<Thread<RingQueue<int>>> *threads,int num,RingQueue<int> &rq)
{Comm(threads,num,rq,productor);
}void ConsumerStart(std::vector<Thread<RingQueue<int>>> *threads,int num,RingQueue<int> &rq)
{Comm(threads,num,rq,consumer);
}void StartAll(std::vector<Thread<RingQueue<int>>> &threads)
{for(auto &thread:threads){std::cout<<"Start:"<<thread.threadname()<<std::endl;thread.Start();}
}void WaitAllThread(std::vector<Thread<RingQueue<int>>> &threads)
{for(auto &thread:threads){thread.Join();}
}int main()
{RingQueue<int> *rq = new RingQueue<int>(10);std::vector<Thread<RingQueue<int>>> threads;ProductorStart(&threads,1,*rq);ConsumerStart(&threads,2,*rq);StartAll(threads);WaitAllThread(threads);return 0;
}

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

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

相关文章

泛微开发修炼之旅--36通过js控制明细表中同一列中多个浏览框的显示控制逻辑(明细表列中多字段显示逻辑控制)

文章链接&#xff1a;36通过js控制明细表中同一列中多个浏览框的显示控制逻辑&#xff08;明细表列中多字段显示逻辑控制&#xff09;

【基于R语言群体遗传学】-13-群体差异量化-Fst

在前几篇博客中&#xff0c;我们深度学习讨论了适应性进化的问题&#xff0c;从本篇博客开始&#xff0c;我们关注群体差异的问题&#xff0c;建议大家可以先看之前的博客&#xff1a;群体遗传学_tRNA做科研的博客-CSDN博客 一些新名词 Meta-population:An interconnected gro…

2024年06月CCF-GESP编程能力等级认证Python编程四级真题解析

本文收录于专栏《Python等级认证CCF-GESP真题解析》,专栏总目录:点这里,订阅后可阅读专栏内所有文章。 一、单选题(每题 2 分,共 30 分) 第 1 题 小杨父母带他到某培训机构给他报名参加CCF组织的GESP认证考试的第1级,那他可以选择的认证语言有几种?( ) A. 1 B. 2 C…

昇思MindSpore学习笔记6-02计算机视觉--ResNet50迁移学习

摘要&#xff1a; 记录MindSpore AI框架使用ResNet50迁移学习方法对ImageNet狼狗图片分类的过程、步骤。包括环境准备、下载数据集、数据集加载、构建模型、固定特征训练、训练评估和模型预测等。 一、概念 迁移学习的方法 在大数据集上训练得到预训练模型 初始化网络权重参数…

通用个人客户关系管理系统设计

设计一个通用个人客户关系管理系统&#xff08;Personal CRM&#xff09;&#xff0c;旨在帮助个人用户管理他们的社交网络、职业联系人、个人项目和日常沟通&#xff0c;需要关注以下几个核心设计原则和功能模块&#xff1a; 核心设计原则 易用性&#xff1a;界面简洁直观&a…

Hospital Management Startup 1.0 SQL 注入漏洞(CVE-2022-23366)

前言 CVE-2022-23366是一个影响HMS v1.0的SQL注入漏洞。该漏洞存在于patientlogin.php文件中&#xff0c;允许攻击者通过特定的SQL注入来获取或修改数据库中的敏感信息。 具体来说&#xff0c;攻击者可以通过向patientlogin.php发送恶意构造的SQL语句来绕过身份验证&#xff…

Java系列-valitile

背景 volatile这个关键字可以说是面试过程中出现频率最高的一个知识点了&#xff0c;面试官的问题也是五花八门&#xff0c;各种刁钻的角度。之前也是简单背过几道八股文&#xff0c;什么可见性&#xff0c;防止指令重拍等&#xff0c;但面试官一句&#xff1a;volatile原理是什…

Echarts实现github提交记录图

最近改个人博客&#xff0c;看了github的提交记录&#xff0c;是真觉得好看。可以移植到自己的博客上做文章统计 效果如下 代码如下 <!DOCTYPE html> <html lang"en" style"height: 100%"><head><meta charset"utf-8"> …

稀疏建模介绍,详解机器学习知识

目录 一、什么是机器学习&#xff1f;二、稀疏建模介绍三、Lasso回归简介四、Lasso超参数调整与模型选择 一、什么是机器学习&#xff1f; 机器学习是一种人工智能技术&#xff0c;它使计算机系统能够从数据中学习并做出预测或决策&#xff0c;而无需明确编程。它涉及到使用算…

OpenCV MEI相机模型(全向模型)

文章目录 一、简介二、实现代码三、实现效果参考文献一、简介 对于针孔相机模型,由于硬件上的限制(如进光量等),他的视野夹角往往有效区域只有140度左右,因此就有研究人员为每个针孔相机前面再添加一个镜片,如下所示: 通过折射的方式增加了相机成像的视野,虽然仍然达不…

神经网络设计过程

1.可根据Iris特征直接判断 2.神经网络方法&#xff0c;采集大量的Iris特征&#xff0c;分类对应标签&#xff0c;构成数据集。 将数据集喂入搭好的神经网络结构&#xff0c;网络通过反向传播优化参数得到模型。 有新的网络送入到模型里&#xff0c;模型会给出识别结果。 3.…

二次开发报错Request method ‘GET’ not supported

环境&#xff1a;springboot3 问题复刻&#xff1a; 前端上传图片的时候&#xff0c;出现了这个报错&#xff0c;离谱 解决方法&#xff1a;修改本地上传文件的路径 没错&#xff0c;路径错误的报错居然是这个&#xff0c;真顶级 是因为你的配置文件中profile这个属性的路径在…

解读‘‘不要卷模型,要卷应用‘‘

前言 2024 年 7 月 4 日&#xff0c;世界人工智能大会暨人工智能全球治理高级别会议全体会议在上海世博中心举行。百度创始人李彦宏在产业发展主论坛上发言&#xff0c;呼吁不要卷模型&#xff0c;要卷应用。 目录 四个要点 积极的观点 不合理性 总结 四个要点 李彦宏的呼吁…

一、openGauss详细安装教程

一、openGauss详细安装教程 一、安装环境二、下载三、安装1.创建omm用户2.授权omm安装目录3.安装4.验证是否安装成功5.配置gc_ctl命令 四、配置远程访问1.配置pg_hba.conf2.配置postgresql.conf3.重启 五、创建用户及数据库 一、安装环境 Centos7.9 x86openGauss 5.0.1 企业版…

普中51单片机:中断系统与寄存器解析(六)

文章目录 引言中断流程图中断优先级下降沿中断结构图中断相关寄存器IE中断允许寄存器&#xff08;可位寻址&#xff09;XICON辅助中断控制寄存器&#xff08;可位寻址&#xff09;TCON标志控制寄存器SCON串行口控制寄存器 中断号中断响应条件中断函数代码模板电路图开发板IO连接…

tensorflow张量生成以及常用函数

张量tensor&#xff1a;多维数组&#xff08;列表&#xff09; 阶&#xff1a;张量的维数 维数 阶 名字 例子 0-D 0 标量 scalar s 1&#xff0c; 2&#xff0c; 3 1-D 1 向量 vector…

Python 的 metaclass

文章目录 先说结论1. metaclass 的作用2. 主要的执行过程 1. metaclass.__new__2. metaclass.__call__关于 metaclass.__init__ 3. metaclass.__prepare__4. 自动创建 __slots__ 属性4.1 metaclass 的接口类4.2 metaclass conflict 5. Class metaprogramming 先说结论 1. meta…

10-windows自带的磁盘上传配额限制?提示这个错误:XXX用户上空间不足,需要XXMB来复制此项目,请删除或移动文件来获得足够的空间如何解决?

1.配置缘由&#xff1a; Windows自带的功能&#xff1a;限制某个磁盘登录的用户上传到这块磁盘的文件容量大小。 2.配置磁盘配额步骤: 右键整块磁盘--属性--配额 3.提示这个错误&#xff1a;XXX用户上空间不足&#xff0c;需要XXMB来复制此项目&#xff0c;请删除或移动文件来…

跨平台桌面应用开发工具:electron的优缺点

跨平台桌面应用开发工具Electron是一个由GitHub开发和维护的开源框架&#xff0c;它允许开发者使用HTML、CSS和JavaScript等Web技术来构建跨平台的桌面应用程序。以下是关于Electron的详细介绍&#xff1a; 一、Electron概述 定义&#xff1a;Electron是一个基于Chromium和Nod…

理解点对点协议:构建高效网络通信

在通信线路质量较差的年代&#xff0c;能够实现可靠传输的高级数据链路控制&#xff08;High-level Data Link Control, HDLC&#xff09;协议曾是比较流行的数据链路层协议。HDLC是一个较复杂的协议&#xff0c;实现了滑动窗口协议&#xff0c;并支持点对点和点对多点两种连接…