[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.rhkb.cn/news/440220.html

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

相关文章

基于SpringBoot+Vue的酒店客房管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

[SAP ABAP] 程序调用

示例数据 学生表(ZDBT_STU_437) 程序&#xff1a; Z437_TEST_20241006 代码如下所示 REPORT Z437_TEST_20241006.* 创建跟表或结构同名的工作区 TABLES: zdbt_stu_437.SELECTION-SCREEN BEGIN OF BLOCK b1 WITH FRAME TITLE TEXT-001.PARAMETERS: p_1 TYPE c LENGTH 4.SELECT-O…

Kali或Debian系统安装JDK1.8保姆级教程

一、下载JDK1.8 先到Oracle的官网下载JDK1.8 Java Archive | Oraclehttps://www.oracle.com/java/technologies/downloads/archive/Java Archive Downloads - Java SE 8

CUDA、Pytorch、Pycharm的安装与配置

文章目录 一、CUDA安装1.检查英伟达驱动支持的最高CUDA版本 二、Pytorch的安装与环境配置1.选择是下载CPU版本还是GPU版本2.上Pytorch官网找到安装命令3.运行指令(1)CPU版本(2)GPU版本 4.验证5.安装其他所需模块(0)安装torch(1)安装Matplotlib(2)安装 pillow&#xff08;可能an…

OSPF的不规则区域

1.远离骨干非骨干区域 2.不连续骨干 解决方案 tunnel ---点到点GRE 在合法与非ABR间建立隧道&#xff0c;然后将其宣告于OSPF协议中&#xff1b; 缺点&#xff1a;1、周期和触发信息对中间穿越区域造成资源占用&#xff08;当同一条路由来自不同区域&#xff0c;路由器会先…

javaScript基础(8个案例+代码+效果图)

目录 1.js常用的输出语句 案例:js初体验 1.代码 2.效果 2.js命名规则 3.js赋值 var 关键字声明变量 案例:交换两个变量的内容 完整代码 效果 4.js数据类型 布尔类型 整数 浮点数 字符串 空型 未定义型 5. 数据检测 6.算术运算符 7.比较运算符 案例:计算圆周长和面积 1.代码 2.…

【游戏模组】重返德军总部2009高清重置MOD,建模和材质全部重置,并且支持光追效果,游戏画质大提升

各位好&#xff0c;今天小编给大家带来一款新的高清重置MOD&#xff0c;本次高清重置的游戏叫《重返德军总部2009》2009年发布&#xff0c;我相信很多玩家已经玩过了&#xff0c;如果你还没有玩过我也可以和你简单介绍一下剧情&#xff0c;这款游戏故事背景接续在《重返德军总部…

Cilium-实战系列-(一)Cilium-安装与部署

前言&#xff1a; 1、首先说一下这篇实战系列和Cilium-ebpf系列文章为 “一文一武”&#xff0c;一个注重点解&#xff0c;一个注重实际操作。 Cilium ebpf 系列文章-什么是ebpf?&#xff08;一&#xff09;_clium ebpf-CSDN博客文章浏览阅读419次。一、We Create a containe…

ElasticSearch备考 -- Multi field

一、题目 Create the index hamlet_2 with one primary shard and no replicas Copy the mapping of hamlet_1 into hamlet_2, but also define a multi-field for speaker. The name of such multi-field is tokens and its data type is the (default) analysed string Reind…

[git] github管理项目之环境依赖管理

导出依赖到 requirements.txt pip install pipreqs pipreqs . --encodingutf8 --force但是直接使用pip安装不了torch&#xff0c;需要添加源&#xff01;&#xff01; pip install -r requirements.txt -f https://download.pytorch.org/whl/torch_stable.htmlpython 项目中 …

基于VirtualBox和Ubuntu的虚拟环境搭建

VirtualBox简介 VirtualBox 是一款开源虚拟机软件。 是由德国 Innotek 公司开发&#xff0c;由Sun Microsystems公司出品的软件&#xff0c;使用Qt编写&#xff0c;在 Sun 被 Oracle 收购后正式更名成 Oracle VM VirtualBox。简单易用&#xff0c;可虚拟的系统包括Windows&…

JavaScript-下篇

上篇我们学习了&#xff0c;一些基础语法和函数&#xff0c;现在我们学习下篇&#xff0c;主要包括,对象和事件。而对象又包括&#xff0c;数组Arrays&#xff0c;String字符串&#xff0c;BOM&#xff0c;DOM等 JS对象 Arrays数组 数组是一种特殊的对象&#xff0c;用于存储…

子比主题美化 – 添加天气教程

前言 经常看到很多的网站顶部或者侧边有显示天气状态的小条幅&#xff0c;看着也美观&#xff0c;寻思着也在自己的小站上显示天气。大体的思路是能识别用的ip地址来确认位置然后以代码形式在前台显示出。 经过在百度上搜索一番&#xff0c;发现一个很不错的天气api&#xff…

Java之二叉树的基本操作实现

1. 模拟实现二叉树前&#xff0c;我们要先表示树&#xff0c;首先定义一个内部类&#xff0c;当作二叉树节点 static class TreeNOde{char val;//存放二叉树的值TreeNOde left;//指向左子树的引用TreeNOde right;//指向右子树的引用//构造方法&#xff0c;用于实例化树的节点p…

Luminar财务造假风波:激光雷达龙头的困境与挑战

近日,美国激光雷达上市公司Luminar被爆出财务造假嫌疑,这一消息震惊了整个行业。Luminar,这家曾风光无限的激光雷达公司,最高市值一度达到120亿美元,其年轻的创始人也因此坐拥豪宅豪车无数。然而,如今在市值仅剩5亿美元左右的时候,却被爆出如此丑闻,令人不禁唏嘘。 带…

成都睿明智科技有限公司抖音电商新蓝海的领航者

在当今这个数字化浪潮汹涌的时代&#xff0c;电商行业正以惊人的速度迭代升级&#xff0c;而抖音电商作为新兴势力&#xff0c;更是凭借其庞大的用户基数、精准的算法推荐和高度互动的社区氛围&#xff0c;成为了众多商家竞相追逐的蓝海市场。在这片充满机遇与挑战的海洋中&…

洛谷每日一题(P1205 [USACO1.2] 方块转换 Transformations)矩阵变换

原题目链接&#xff1a; P1205 [USACO1.2] 方块转换 Transformations - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 原题目截图&#xff1a; 思路分析&#xff1a; 这题目还是比较简单&#xff0c;模拟一下旋转变化的过程&#xff0c;然后注意变换的规律就行了。 读取输入…

【移动端】事件基础

一、移动端事件分类 移动端事件主要分为以下几类&#xff1a; 1. 触摸事件&#xff08;Touch Events&#xff09; 触摸事件是移动设备特有的事件&#xff0c;用来处理用户通过触摸屏幕进行的操作。主要的触摸事件有&#xff1a; touchstart&#xff1a;手指触摸屏幕时触发。…

我的项目管理生涯

1 前言 从好几年前就想写几篇关于自己职业生涯的文章了&#xff0c;一直由于各种原因没有写成&#xff0c;正好借新的工作机会&#xff0c;尤其是项目管理这段工作经历&#xff0c;计划通过这一二篇文章进行总结和反思一下&#xff0c;以期更顺利的开展相关工作或是自己能更上…

ad.concat()学习

学习1 import anndata as ad, pandas as pd, numpy as np from scipy import sparse a ad.AnnData(Xsparse.csr_matrix(np.array([[0, 1], [2, 3]])),obspd.DataFrame({"group": ["a", "b"]}, index["s1", "s2"]),varpd.D…