[Linux]:线程(三)

img

✨✨ 欢迎大家来到贝蒂大讲堂✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:Linux学习
贝蒂的主页:Betty’s blog

1. POSIX 信号量

1.1 信号量的概念

为了解决多执行流访问临界区,造成数据不一致等问题,我们除了使用互斥锁外,我们还可以使用一种 POSIX信号量的方法。

当我们运用互斥锁来保护临界资源时,意味着我们把这块临界资源视为一个不可分割的整体,在同一时刻只准许一个执行流对其进行访问。

其实我们也能将这块临界资源进一步划分成多个区域。当多个执行流有访问临界资源的需求时,若让这些执行流同时去访问临界资源的不同区域,此时也并不会引发数据不一致等问题。信号量就是基于此的解决方法。

POSIX信号量本质上是一个计数器,用于衡量临界资源中的资源数目。它对临界资源内部的资源数进行统计,同时操作系统为其提供了一种对临界资源的预定机制。所有执行流在访问临界资源之前,必须先申请信号量。

画板

信号量的 PV 操作:

  • P操作:我们将申请信号量称为P操作,申请信号量的本质就是申请获得临界资源中某块资源的使用权限,当申请成功时临界资源中资源的数目应该减一,因此P操作的本质就是让计数器减一。
  • V操作:我们将释放信号量称为V操作,释放信号量的本质就是归还临界资源中某块资源的使用权限,当释放成功时临界资源中资源的数目就应该加一,因此V操作的本质就是让计数器加一。

并且由于因信号量的 PV 操作同样属于临界资源,所以 PV 操作肯定是原子的。

值得注意的是: 虽然 POSIX信号量和SystemV信号量作用相同,都是用于同步操作,但POSIX信号量常用于线程间同步,而 SystemV 信号量常用于进程间通信。

1.2 信号量的接口

1.2.1 初始化信号量

我们首先需要使用 sem_init初始化信号量,其用法如下:

  1. 函数接口:int sem_init(sem_t *sem, int pshared, unsigned int value);
  2. 参数:
  • sem:需要初始化的信号量。
  • pshared:传入0值表示线程间共享,传入非零值表示进程间共享。
  • value:信号量的初始值(计数器的初始值)。
  1. 返回值:初始化信号量成功返回0,失败返回-1。
1.2.2 销毁信号量

在使用完信号量之后,我们就需要用 sem_destory 对其进行销毁,其用法如下:

  1. 函数接口:int sem_destroy(sem_t *sem);
  2. 参数:
  • sem:需要销毁的信号量。
  1. 返回值:销毁信号量成功返回0,失败返回-1。
1.2.3 申请信号量

申请信号量也就是 P 操作,我们需要使用 sem_wait函数,其用法如下:

  1. 函数接口:int sem_wait(sem_t *sem);
  2. 参数:
  • sem:需要申请的信号量。
  1. 返回值:申请信号量成功返回0,信号量的值减一。申请信号量失败返回-1,信号量的值保持不变。如果信号量为 0,则该执行流会被阻塞,直至信号量大于 0。
1.2.4 释放信号量

释放信号量也就是 V 操作,我们需要使用 sem_post函数,其用法如下:

  1. 函数接口:int sem_post(sem_t *sem);
  2. 参数:
  • sem:需要释放的信号量。
  1. 返回值:释放信号量成功返回0,信号量的值加一。释放信号量失败返回-1,信号量的值保持不变。

如果信号量的初始值为1,那么此时信号量所描述的临界资源只有一份,这个临界资源也只能同时被一个执行流访问。此时信号量的作用基本等价于互斥锁,这种信号量我们称为二元信号量。

比如我们下面可以通过二元信号量实现我们的抢票逻辑:

#include <iostream>
#include <cstdio>
#include <string>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
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;
};
int tickets = 1000;
Sem sem(1);
void *getTickets(void *args)
{uint64_t i = (uint64_t)args;char buffer[64] = {0};snprintf(buffer, sizeof(buffer), "thread %llu", i);while (true){sem.P();if (tickets > 0){usleep(1000);std::cout << buffer << " get a ticket,tickets left: " << --tickets << std::endl;sem.V();}else{sem.V();break;}}std::cout << buffer << " quit ..." << std::endl;return nullptr;
}
int main()
{pthread_t tids[5];for (uint64_t i = 0; i < 5; i++){pthread_create(tids + i, nullptr, getTickets, (void *)i);}for (int i = 0; i < 5; i++){pthread_join(tids[i], nullptr);}return 0;
}

2. 生产者消费者模型

2.1 概念

生产者 - 消费者模型是一种经典的多线程或多进程同步模型。它主要用于解决在数据生产和数据消费速度不一致的情况下,如何安全、高效地处理数据的问题。

在这个模型中,有两类角色:生产者消费者。生产者负责生产数据,例如在一个文件读取系统中,生产者可能是读取文件内容并将其转换为特定格式数据的线程或进程;消费者则负责消费(处理)生产者生产的数据,比如将读取到的数据进行进一步的分析或者存储到数据库中的线程或进程。

画板

利用该模型我们能实现生产者与消费者之间的解耦,并且生产者在生产时,其它生产者可以获取数据,消费者可以处理数据,消费者在消费时也是同理,一定程度上实现了并发。

2.2 特点

生产者-消费者模型一般具有以下三个特点:

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

因为容器是能够被多个执行流访问的一个共享资源,所以生产者与生产者,消费者与消费者,生产者与消费者之间是一个互斥关系,而我们访问数据一定是生产者先生产,消费者再消费,所以生产者与消费者之间是一个同步关系。

3. 生产者消费者模型的实现

3.1 基于阻塞队列实现

阻塞队列就是队列的一种,但其要求:

  • 当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中放入了元素。
  • 当队列满时,往队列里存放元素的操作会被阻塞,直到有元素从队列中取出。

其中阻塞队列最典型的应用场景实际上就是管道的实现。

画板

首先我们可以先实现 BlockingQueue的框架:首先我们需要一个队列 _q 作为成员变量以及表示其容量的 _cap,并且因为涉及多执行流访问,需要一把互斥锁 _mutex,最后我们还需要两个条件变量 _empty与·_full分别表示当我们队列为空时,执行消费的执行流需加入 _empty 条件变量与当我们队列为满时,执行生产的执行流需加入该条件变量 _full

#include <iostream>
#include <pthread.h>
#include <queue>
#include <unistd.h>
const int defaultnum = 5;
template <class T>
class BlockQueue
{bool IsFull(){return _q.size() == _cap;}bool IsEmpty(){return _q.empty();}
public:BlockQueue(int cap = defaultnum): _cap(cap){pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_full, nullptr);pthread_cond_init(&_empty, nullptr);}~BlockQueue(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_full);pthread_cond_destroy(&_empty);}void Push(const T&data);void Pop(T&data);
private:std::queue<T> _q;int _cap;pthread_mutex_t _mutex;pthread_cond_t _full;pthread_cond_t _empty;
};

并且我们实现生产 Push与消费 Pop操作也十分简单,生产时如果队列为满,则加入条件变量 _full 等待,没有则正常生产,生产完毕后该队列一定有数据,这时我们就需要唤醒 _empty条件变量执行消费操作。而消费操作正好对应,如果消费时如果队列为空,则加入条件变量 _empty 等待,否则正常消费,消费完毕后该队列一定不为空,这时我们就需要唤醒 _full条件变量执行生产操作。并且生产与消费操作都属于临界资源,所以需要加锁。

void Push(const T&data)
{pthread_mutex_lock(&_mutex);while(IsFull()){pthread_cond_wait(&_full,&_mutex);}_q.push(data);pthread_mutex_unlock(&_mutex);pthread_cond_signal(&_empty);
}
void Pop(T&data)
{pthread_mutex_lock(&_mutex);while(IsEmpty()){pthread_cond_wait(&_empty,&_mutex);}data=_q.front();_q.pop();pthread_mutex_unlock(&_mutex);pthread_cond_signal(&_full);
}

需要注意的是,<font style="color:rgb(28, 31, 35);">pthread_cond_wait</font> 函数作为让当前执行流进行等待的函数,存在调用失败的可能性,若调用失败,该执行流会继续往后执行。

在多生产者的情形下,当消费者消费了一个数据后,若使用 <font style="color:rgb(28, 31, 35);">pthread_cond_broadcast</font> 函数唤醒多个生产者,此时若阻塞队列仅有一个空位,且唤醒的生产者与消费者竞争,当生产者持续竞争锁成功时,就可能出现错误。鉴于此,为避免上述情况发生,必须让线程被唤醒后再次进行判断,以确认是否真正满足生产消费条件,所以这里要用 <font style="color:rgb(28, 31, 35);">while</font> 进行判断。

最后我们创建多个线程,进行对应的生产与消费操作即可。

#include "BlockQueue.hpp"
#include <cstdlib>
#include <ctime>
void *Producer(void *args)
{pthread_detach(pthread_self());BlockQueue<int> *bq = static_cast<BlockQueue<int> *>(args);while (true){int data = rand() % 100 + 1;bq->Push(data); std::cout << "Producer: " << data << std::endl;}
}
void *Consumer(void *args)
{pthread_detach(pthread_self());BlockQueue<int> *bq = static_cast<BlockQueue<int> *>(args);while (true){int data = 0;bq->Pop(data); std::cout << "Consumer: " << data << std::endl;sleep(1);}
}
int main()
{srand((unsigned int)time(nullptr));BlockQueue<int> *bq = new BlockQueue<int>;for (int i = 0; i < 5; i++){pthread_t tid;pthread_create(&tid, nullptr, Producer, bq);}for (int i = 0; i < 5; i++){pthread_t tid;pthread_create(&tid, nullptr, Consumer, bq);}while(true);return 0;
}

3.2 基于循环队列实现

我们同样也可以通过循环队列来实现生产者消费者模型,并且在循环队列不为空或者满的情况下,生产者与消费者可以同步执行。并且要求:

  • 当生产和消费指向同一个资源的时候,只能一个执行流访问。为空的时候,由生产者去访问;为满的时候,由消费者去访问。
  • 消费者不能超过生产者。
  • 生产者不能把消费者套圈,因为这样会导致数据被覆盖。

画板

首先我们可以先实现 RingQueue的框架:首先我们可以使用数组来模仿队列 _q ,以及表示其容量的 _cap,然后用 _p_pos_c_pos分别表示生产者与消费者访问数据的下标,其中我们需要两个信号量 _blank_sem与·_data_sem分别表示队列未填数据的个数与已填数据的个数,并且因为涉及多执行流访问,我们最后要用两把互斥锁 _p_mutex_c_mutex来保护生产与消费的临界资源。

#pragma once
#include <iostream>
#include <pthread.h>
#include <vector>
#include <semaphore.h>
#include <unistd.h>
const int defaultnum = 5;
template <class T>
class RingQueue
{void P(sem_t &s){sem_wait(&s);}void V(sem_t &s){sem_post(&s);}void Lock(pthread_mutex_t *mutex){pthread_mutex_lock(mutex);}void UnLock(pthread_mutex_t *mutex){pthread_mutex_unlock(mutex);}public:RingQueue(int cap = defaultnum): _cap(cap), _p_pos(0), _c_pos(0){_q.resize(_cap);sem_init(&_blank_sem, 0, _cap);sem_init(&_data_sem, 0, 0);pthread_mutex_init(&_p_mutex, nullptr);pthread_mutex_init(&_c_mutex, nullptr);}~RingQueue(){sem_destroy(&_blank_sem);sem_destroy(&_data_sem);pthread_mutex_destroy(&_p_mutex);pthread_mutex_destroy(&_c_mutex);}void Push(const T &data);void Pop(T &data);
private:std::vector<T> _q;int _cap;int _p_pos;int _c_pos;sem_t _blank_sem;sem_t _data_sem;pthread_mutex_t _p_mutex;pthread_mutex_t _c_mutex;
};

我们实现生产 Push与消费 Pop操作也十分简单,生产时如果队列为满,那么未填数据个数 _blank_sem为 0,该执行流就会被阻塞,没有则正常生产。而消费操作正好对应,如果消费时如果队列为空,那么已填数据个数 _data_sem为 0,该执行流就会被阻塞,否则就正常消费,并且生产与消费操作都属于临界资源,所以需要加锁。

void Push(const T &data)
{P(_blank_sem);Lock(&_p_mutex);_q[_p_pos] = data;_p_pos++;_p_pos %= _cap;UnLock(&_p_mutex);V(_data_sem);
}
void Pop(T &data)
{P(_data_sem);Lock(&_c_mutex);data = _q[_c_pos];_c_pos++;_c_pos %= _cap;UnLock(&_c_mutex);V(_blank_sem);
}

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

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

相关文章

Nuxt.js 应用中的 app:mounted 钩子详解

title: Nuxt.js 应用中的 app:mounted 钩子详解 date: 2024/10/5 updated: 2024/10/5 author: cmdragon excerpt: app:mounted 钩子在 Vue 应用的生命周期中扮演着重要角色,提供了在组件被挂载后的执行时机。通过合理利用这个钩子,我们能够提高组件的交互性、用户体验以及…

华为OD机试 - 核酸最快检测效率 - 动态规划、背包问题(Python/JS/C/C++ 2024 E卷 200分)

华为OD机试 2024E卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试真题&#xff08;Python/JS/C/C&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;私信哪吒&#xff0c;备注华为OD&#xff0c;加入华为OD刷题交流群&#xff0c;…

基于单片机的智能浇花系统

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 基于51单片机&#xff0c;采样DHT11温湿度传感器检测温湿度&#xff0c;通过LCD1602显示 4*4按键矩阵可以设置温度湿度阈值&#xff0c;温度大于阈值则开启水泵&#xff0c;湿度大于阈值则开启风扇…

基于STM32的智能窗帘控制系统设计

引言 本项目将基于STM32微控制器设计一个智能窗帘控制系统&#xff0c;用户可以通过按钮或遥控器控制窗帘的开关&#xff0c;并且系统能够根据光照强度自动调节窗帘的开合状态。该项目展示了STM32微控制器在家居自动化中的应用&#xff0c;以及与光照传感器、直流电机和红外接…

Linux网络编程 -- 网络基础

本文主要介绍网络的一些基础概念&#xff0c;不涉及具体的操作原理&#xff0c;旨在构建对网络的基础认识。 1、网络的早期发展历程 20世纪50年代 在这一时期&#xff0c;计算机主机非常昂贵&#xff0c;而通信线路和设备相对便宜。为了共享计算机主机资源和进行信息的综合处…

模拟器GSN3之DHCP动态分配IP地址配置案例

前文《详解DHCP服务工作原理及配置案例》介绍了DHCP服务工作原理&#xff0c;要想彻底理解、应用DHCP服务&#xff0c;须通过实证案例学习&#xff0c;该文在GSN3虚拟环境下&#xff0c;构建DHCP服务的环境。 一、配置环境&#xff1a; 1、GSN3 2、路由器&#xff1a;R1、R2…

冥想第一千三百零一天(1301)

1.今天上午溪溪和小侄子写作业&#xff0c;我带着桐桐去了惠济区的裕华广场永辉&#xff0c;给家人买了好吃的&#xff0c;下午4点半左右去了妈妈朋友家里摘石榴。 2.感谢父母&#xff0c;感谢朋友&#xff0c;感谢家人&#xff0c;感谢不断进步的自己。

[C++]使用纯opencv部署yolov11旋转框目标检测

【官方框架地址】 GitHub - ultralytics/ultralytics: Ultralytics YOLO11 &#x1f680; 【算法介绍】 YOLOv11是一种先进的对象检测算法&#xff0c;它通过单个神经网络实现了快速的物体检测。其中&#xff0c;旋转框检测是YOLOv11的一项重要特性&#xff0c;它可以有效地检…

利用 Python 爬虫采集 1688商品详情

1688是中国的一个大型B2B电子商务平台&#xff0c;主要用于批发和采购各种商品。对于需要从1688上获取商品详情数据、工程数据或店铺数据的用户来说&#xff0c;可以采用以下几种常见的方法&#xff1a; 官方API接口&#xff1a;如果1688提供了官方的API接口&#xff0c;那么可…

FinOps现状分析:行业趋势与未来展望

一、FinOps 的国内现状 《FinOps 现状》是 FinOps 基金会自 2020 年以来开展的一项年度调查&#xff0c;旨在收集对关键优先、行业趋势和 FinOps 实践方向 的见解。该调查有助于为 FinOps 基金会的活动提供信息&#xff0c;并为更广泛的市场提供有关 FinOps 在各种组织中如何实…

redhat7.7 linux 网络配置文件

一、为什么虚拟网卡配置文件是ens33 变更目录至网络脚本&#xff08;network-scripts&#xff09;文件夹&#xff0c;发现网络配置文件名称为“ifcfg-ens33” cd /etc/sysconfig/network-scripts ls扩展&#xff1a;“ifcfg-ens33”文件下面还有一个“ifcfg”前缀的文件&…

线程互斥函数的例子

代码 #include<stdio.h> #include<pthread.h> #include<sched.h> void *producter_f(void *arg); void *consumer_f(void *arg); int buffer_has_item0; pthread_mutex_t mutex; int running1; int main(void) {pthread_t consumer_t;pthread_t producter_t…

【ubuntu】APT、apt、apt-get介绍

目录 1.apt简介 2.常用apt指令 2.1安装 2.2更新列表 2.3更新已经安装的软件包 2.4搜索软件包 2.5显示软件包信息 2.6移除软件包 2.7清理无用的安装包 2.8清理无用的依赖项 3.apt和apt-get 3.1区别 3.2 总结 1.apt简介 apt的全称是advanced package …

大学生就业桥梁:基于Spring Boot的招聘系统

1系统概述 1.1 研究背景 如今互联网高速发展&#xff0c;网络遍布全球&#xff0c;通过互联网发布的消息能快而方便的传播到世界每个角落&#xff0c;并且互联网上能传播的信息也很广&#xff0c;比如文字、图片、声音、视频等。从而&#xff0c;这种种好处使得互联网成了信息传…

No.1 | 从小白到入门:我的渗透测试笔记

嘿&#xff0c;小伙伴们&#xff01;好久不见啊&#xff0c;是不是都以为我失踪了&#xff1f;&#x1f602; 其实呢&#xff0c;最近一直在埋头苦学&#xff0c;感觉自己就像是在技术的海洋里游泳&#xff0c;每天都在吸收新知识。现在终于有时间冒个泡&#xff0c;跟大家分享…

脱口秀演员调侃王楚钦引争议

听说脱口秀演员调侃王楚钦输球&#xff0c;野生喜剧回应暂停演出合作&#xff0c;这不仅引发了关于脱口秀表演冒犯边界的讨论&#xff0c;也让我们反思言论自由与尊重他人之间的界限。 脱口秀作为一种艺术形式&#xff0c;其核心在于通过幽默、讽刺的方式&#xff0c;对社会现象…

Meta MovieGen AI:颠覆性的文本生成视频技术详解

近年来&#xff0c;生成式AI技术的发展迅猛&#xff0c;尤其是在文本生成图像、文本生成视频等领域。Meta公司近期推出的MovieGen AI&#xff0c;以其强大的文本生成视频能力震撼了整个AI行业。本文将详细解读Meta MovieGen AI的核心技术、功能特性及其在实际应用中的潜力。 一…

Mac 安装OpenAI的开源语音神器Whisper

一.Whisper 项目地址 1.GitHub项目地址 https://github.com/openai/whisper二.Whisper项目简介 Whisper 是 OpenAI 开源的语音神器&#xff0c;可以实现识别音频、视频中的人声&#xff0c;并将人声转换为字幕内容&#xff0c;保存到文件&#xff1b; 三.Whisper 安装教程 …

一“填”到底:深入理解Flood Fill算法

✨✨✨学习的道路很枯燥&#xff0c;希望我们能并肩走下来! 文章目录 目录 文章目录 前言 一 floodfill算法是什么&#xff1f; 二 相关OJ题练习 2.1 图像渲染 2.2 岛屿数量 2.3 岛屿的最大面积 2.4 被围绕的区域 2.5 太平洋大西洋水流问题 2.6 扫雷游戏 2.7 衣橱整…

Fastjson反序列化

Fastjson反序列化一共有三条利用链 TempLatesImpl&#xff1a;实战中不适用JdbcRowSetImpl&#xff1a;实际运用中较为广泛BasicDataSource&#xff08;BCEL&#xff09; 反序列化核心 反序列化是通过字符串或字节流&#xff0c;利用Java的反射机制重构一个对象。主要有两种…