Linux线程补充之——同步
一、Linux线程同步
同步是相对于竞争的概念;
同步就是在保证安全的前提下啊,按照一定的顺序访问临界资源;
所有的资源一定是先访问的临界资源,申请失败然后才进行排队的;互斥锁保证的是来访问的进程只允许有一个进入,剩下的都失败然后按照一定的顺序进行排队,排队之后所就不管了,除非线程被唤醒;换句话说互斥是前提,同步是结果;
同步的方式:条件变量依赖互斥锁使用;
1.1条件变量
条件变量:1.提供一种简单的通知机制;2.提供一个等待队列,即使用队列的方式使得线程之间具有一定的顺序;
互斥锁和条件变量都是需要被管理起来的,即原生线程库会为它们创建对应的内核结构,然后使用数据结构将它们维护起来;即凡是可以创建多个对象的,都要进行管理;
线程执行传参函数是尽量保证不要传递主线程栈内元素的地址,防止被影响所有最好传递的是拷贝;
typedef union
{struct{int __lock;unsigned int __futex;__extension__ unsigned long long int __total_seq;__extension__ unsigned long long int __wakeup_seq;__extension__ unsigned long long int __woken_seq;void *__mutex;//内部有mutex是有一定的耦合度的unsigned int __nwaiters;unsigned int __broadcast_seq;} __data;char __size[__SIZEOF_PTHREAD_COND_T];__extension__ long long int __align;
} pthread_cond_t;
1.1.1条件变量的初始化和释放
#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);//条件变量的释放
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);//条件变量的申请
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;//全局或者静态条件变量的申请和释放
1.1.2条件变量的等待
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
//将申请锁失败的线程进行排队,第二个参数是锁;
1.1.3条件变量的唤醒等待
线程被唤醒后会去上次等待的地方继续往下执行;
#include <pthread.h>
int pthread_cond_broadcast(pthread_cond_t *cond);//唤醒所有队列中的线程
int pthread_cond_signal(pthread_cond_t *cond);//唤醒队首线程
//线程被唤醒后需要重新获取锁;
1.1.4使用
pthread_mutex_lock(&mutex);//用来阻塞申请不成功的线程
pthread_cond_wait(&cond, &mutex);//先释放锁再排队
//将被阻塞的线程进行排队,传递锁的原因就是进入等待队列是会锁所自动释放,否则就进入等待就没有锁了,导致死锁问题;
如上操作最终就转变成了,所有新线程不管是否申请锁成功都是先进行排队,控制逻辑交给主线程完成;通过创建完线程每一个线程睡眠一小会,来控制顺序;
1.2同步周边问题
1.2.1为什么要让线程休眠(排队)
因为临界资源不就绪,临界资源是二元的就绪和不就绪,就绪可以访问,不就绪不可以访问,此时最好休眠就行,不要一直申请做无效的事;
根据临界资源的状态判断来决定线程是否排队并且排队的线程是否被唤醒;临界资源要保证共享资源加互斥;
即最终访问临界资源的方式就变成了先对临界资源进行判断,所以要在判断前就加锁,然后分情况1.在临界资源就绪的情况下只允许一个执行流访问;2.在临界资源不就绪的情况下,执行的是同步而不是竞争;
二、CP模型(consumer producer)
CP模型是一个安全高效实现同步互斥的模型;
总结:表现为三种关系两种角色和一个通信场所;单执行流传参会使得耦合度过高,而一个执行流将参数传到缓冲区,另一个执行流获取参数本质上就是解耦合传参的设计;
2.1生活中的CP模型
超市:1.需求大,满足生产者一次性大量生产,因为生产少,生产作业线就没有意义,需求少,生产多了就浪费;2.存在现成的商品,满足消费者即需即有的特点;本质上超市就是将数据进行缓存;支持忙闲不均(可以实现并发),生产和消费解耦合;
生产者:关心超市剩余空间;
消费者:关心超市剩余商品;
2.2计算机中的CP模型
本质是计算机中的执行流进行通信并且是安全高效的通信,会存在并发问题;
数据的生命周期包括,产生数据、发送数据、读取数据、处理数据;要注意的是:1.数据发送以大批量的方式可以提高效率;2产生数据是需要消耗时间的;
CP在计算机中都是线程,超市就是一块特定的内存空间,通信要求的共享资源,商品就是数据;
存在并发问题的场景:
1.生产者之间;2消费者之间;生产者和消费者之间;
生产者:先互斥后竞争;
消费者:先互斥后竞争;
生产者和消费者:先互斥后同步(先生产后消费,因为数据是生产者生产的,生产者对数据的竞争能力强,会导致消费者长时间饥饿);
2.3CP模型实现方式
2.3.1基于阻塞队列的CP模型
在多线程编程中阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构。其与普通的队列区别在于,当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素;当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出,管道类似于阻塞队列;
存在伪唤醒的情况,由于多个生产线程被唤醒后对于锁的竞争能力较强,会导致消费线程长时间得不到CPU产生饥饿,而且竞争成功的生产线程会从wait的位置继续向后执行,这样会因为没有从临界资源的判断开始执行产生数据不一致问题;即对于临界资源的判断使用循环方式,这样从wait之后执行直接进行下一次的判断;
#pragma once
#include <queue>
#include <pthread.h>
template <class T>
class blockqueue
{static const int defaultnum = 20;public:blockqueue(int maxcapacity = defaultnum) : maxcapacity_(maxcapacity){pthread_mutex_init(&mutex_, nullptr);pthread_cond_init(&p_cond_, nullptr);pthread_cond_init(&c_cond_, nullptr);low_water = 5;high_water = 15;}~blockqueue(){pthread_mutex_destroy(&mutex_);pthread_cond_destroy(&p_cond_);pthread_cond_destroy(&c_cond_);}public:T pop(){pthread_mutex_lock(&mutex_);while (q_.size() == 0){pthread_cond_wait(&c_cond_, &mutex_);}T ret = q_.front();q_.pop();if (q_.size() <= low_water)pthread_cond_signal(&p_cond_);pthread_mutex_unlock(&mutex_);return ret;}void push(const T &value){pthread_mutex_lock(&mutex_);while (q_.size() == maxcapacity_){pthread_cond_wait(&p_cond_, &mutex_);}q_.push(value);if (q_.size() >= high_water)pthread_cond_signal(&c_cond_);pthread_mutex_unlock(&mutex_);}private:std::queue<T> q_; // 本质上还一个共享资源,是有线程安全的int maxcapacity_; // 用于临界资源判断是否就绪// int mincapacity_; // 用于临界资源判断是否就绪pthread_mutex_t mutex_; // 由于queue容器是不安全的,所以想要加锁保护pthread_cond_t p_cond_; // 由于生产者和消费者之间的关系是不一样的需要进行判断是同步和竞争pthread_cond_t c_cond_;int low_water;int high_water;
};
2.4正确看待CP模型的高效问题
高效体现在当前线程发送数据的同时其他线程可以处理数据;当前线程读取数据的同时其他线程可以生产数据;而发送数据和读取数据即生产者和消费者之间本质上是串行的;
三、POSIX信号量
阻塞队列中的队列是当做一份共享资源使用的;通过加锁保证共享资源的安全性;有时候共享资源也可以被看作多份;分成多少个小块共享资源就最多允许多少个线程访问一整个共享资源;使用信号量保证块共享资源数量和线程的数量匹配并且保证每一个线程访问的是不同的块共享资源;申请和释放信号量要求必须是原子的;可以使用互斥锁保证信号量的PV操作是原子的;访问一个划分了多个区域的共享资源要先申请信号量;
使用信号量执行PV操作之后就不需要对临界资源是否就绪进行判断了,因为申请信号量的时候就已经进行了临界资源的判断了;
3.1信号量的初始化和释放
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);//信号量的初始化
//第一个参数表示信号量;
//第二个参数pshared为0表示允许线程间进行通信,为1表示进程间进行通信;
//第三个参数表示信号量的初始值;
int sem_destroy(sem_t *sem);//信号量的释放
//参数是信号量
3.2信号量的PV操作
#include <semaphore.h>
int sem_wait(sem_t *sem);//阻塞式地等待,P操作将信号量-1;
int sem_trywait(sem_t *sem);//非阻塞地等待,失败直接返回;int sem_post(sem_t *sem);//发布信号量,V操作,将信号量+1;
四、基于环形队列的生产消费模型
4.1环形队列的原理
使用数组模拟环形队列,即使用下标++并且%=数组长度;
使用两个下标(head tail)来维护这个数组,当生产一个数据,*head = value,head ++ 并 head %= size,当消费一个数据*tail = 0,tail ++ 并 tail %= size;
需要注意的是环形队列空和满时,两个下标都是指向同一个位置,会导致不容易判断为空还是满;
解决方式:
1.使用计数器维护,默认为0,push数据就++,pop数据就–;2空一个位置,如果head+1等于tail就停止;
4.2环形队列生产消费模型的原理
三个原则:
1.指向同一个位置是不可以同时访问;
2.消费者速度不能比生产者快;
3.生产者不能超过消费者一圈;
即不空或者不满的时候是可以并发的发送数据和读取数据的,指向同一个位置是必须等待,为空时消费者等待生产者生产,满是生产者等待消费者消费;
4.3信号量实现环形队列生产消费模型
对于生产者用剩余空间的数量来设置信号量spacesem,默认大小为环形队列的大小,对于消费者使用剩余数据数量来设置信号量datasem,默认大小为0;
1.指向同一个位置时,其中的一个信号量一定为零,导致不可以进行P操作,也就是满足同一位置不能同时访问;
2.因为spacesem开始不为零,所以消费者的速度不能比生产者快;因为spacesem为0时,datasem大小为环形队列的大小,这时生产者就无法P成功,也就是说生产者不可能超过消费者一圈;
3.当执行的不是同一个位置时,也就是不空或者不满时,由于信号量都不为0,所以都会申请信号量成功允许并发访问;
生产者先申请spacesem,然后生产成功执行Vdatasem,因为生产成功对于消费者就是多了一个消费数据;然后消费者就可以先申请datasem,消费成功执行Vspacesem;
注意数组的空间大小保持不变,只是下标变化;由于多个线程对于下标访问存在线程安全,所以需要进行加锁;
生产者和消费者如果使用一把锁就会导致生产者和消费者无法并发发送和读取数据;所以使用两把锁;另外使用锁要在PV操作之间,因为1.信号量PV本身是原子的。2.PV操作和锁的申请释放应该是并行的;
总结:对于环形队列CP模型大部分时间只有两个线程,一个生产者和一个消费者,队列空或者满的时候,只有一个线程,生产者或消费者;而阻塞队列CP模型则任何时刻只有一个线程;
#pragma once#include <iostream>
#include <vector>
#include <semaphore.h>
#include <pthread.h>template <class T>
class ringqueue
{
public:ringqueue(int capacity = defaultnum) : v_(capacity), capacity_(capacity), c_step_(0), p_step_(0){sem_init(&c_data_sem_, 0, 0);sem_init(&p_space_sem_, 0, capacity_);pthread_mutex_init(&cmutex_, nullptr);pthread_mutex_init(&pmutex_, nullptr);}void push(const T &value) // 发送数据{p(&p_space_sem_);lock(&pmutex_);v_[p_step_++] = value;p_step_ %= capacity_;unlock(&pmutex_);v(&c_data_sem_);}void pop(T *out) // 读取数据{p(&c_data_sem_);lock(&cmutex_);*out = v_[c_step_++];c_step_ %= capacity_;unlock(&cmutex_);v(&p_space_sem_);}~ringqueue(){sem_destroy(&c_data_sem_);sem_destroy(&p_space_sem_);pthread_mutex_destroy(&cmutex_);pthread_mutex_destroy(&pmutex_);}private:void p(sem_t *sem){sem_wait(sem);}void v(sem_t *sem){sem_post(sem);}void lock(pthread_mutex_t *mutex){pthread_mutex_lock(mutex);}void unlock(pthread_mutex_t *mutex){pthread_mutex_unlock(mutex);}private:std::vector<T> v_; // 只用数组来模拟环形队列int capacity_; // 用来维护数组的大小int c_step_; // 消费者下标int p_step_; // 生产者下标sem_t c_data_sem_; // 消费信号量sem_t p_space_sem_; // 生产信号量pthread_mutex_t cmutex_;pthread_mutex_t pmutex_;static const int defaultnum = 20; // 成员常量
};
相关文章:

Linux线程补充之——同步
一、Linux线程同步 同步是相对于竞争的概念; 同步就是在保证安全的前提下啊,按照一定的顺序访问临界资源; 所有的资源一定是先访问的临界资源,申请失败然后才进行排队的;互斥锁保证的是来访问的进程只允许…...
面试九 设计模式
单例模式通常被归类为创建型设计模式,因为它主要关注如何创建对象的实例,以及如何确保在整个应用程序生命周期中只有一个实例存在。 1.为什么日志模块和数据库连接池需要单例模式 使用单例模式来实现数据库连接池主要有以下几个原因: 全局唯…...
c++和c语言的区别实例
C和C语言在程序设计领域内具有深远的影响,它们不仅丰富了编程的世界,也为软件开发人员提供了强大的工具。虽然C是在C语言的基础上发展起来的,但两者之间存在着一些关键的区别。为了更深入地理解这些不同,本文将从多个维度探讨C和C…...

图论基础|841.钥匙和房间、463. 岛屿的周长
目录 841.钥匙和房间 思路:本题是一个有向图搜索全路径的问题。 只能用深搜(DFS)或者广搜(BFS)来搜。 463. 岛屿的周长 841.钥匙和房间 力扣题目链接 (opens new window) 有 N 个房间,开始时你位于 0…...

把 Taro 项目作为一个完整分包,Taro项目里分包的样式丢失
现象: 当我们把 Taro 项目作为原生微信小程序一个完整分包时,Taro项目里分包的样式丢失,示意图如下: 原因: 在node_modules/tarojs/plugin-indie/dist/index.js文件里,限制了只有pages目录下会被引入app.w…...

腾讯云服务器价格查询系统,2024年1年、3年和5年活动价格表
腾讯云服务器多少钱一年?61元一年起。2024年最新腾讯云服务器优惠价格表,腾讯云轻量2核2G3M服务器61元一年、2核2G4M服务器99元一年可买三年、2核4G5M服务器165元一年、3年756元、轻量4核8M12M服务器646元15个月、4核16G10M配置32元1个月、312元一年、8核…...

第十四届蓝桥杯大赛软件赛省赛Java大学B组
最近正在备考蓝桥杯,报的java b组,顺便更一下蓝桥的 幸运数字 题目 思路:填空题,暴力即可 import java.util.Scanner; // 1:无需package // 2: 类名必须Main, 不可修改public class Main {static int trans(int x, int y){int …...

Java二阶知识点总结(七)SVN和Git
SVN 1、SVN和Git的区别 SVN是集中式的,也就是会有一个服务器保存所有代码,拉取代码的时候只能从这个服务器上拉取;Git是分布式的,也就是说每个人都保存有所有代码,如果要获取代码,可以从其他人手上获取SV…...

Java后端八股------设计模式
Coffee可以设计成接口。...

DBO优化GRNN回归预测(matlab代码)
DBO-GRNN回归预测matlab代码 蜣螂优化算法(Dung Beetle Optimizer, DBO)是一种新型的群智能优化算法,在2022年底提出,主要是受蜣螂的的滚球、跳舞、觅食、偷窃和繁殖行为的启发。 数据为Excel股票预测数据。 数据集划分为训练集、验证集、测试集,比例…...
Day 31 贪心01
理论基础 贪心的本质是选择每一阶段的局部最优,从而达到全局最优。最好用的策略就是举反例,如果想不到反例,那么就试一试贪心吧。 贪心算法一般分为如下四步: 将问题分解为若干个子问题找出适合的贪心策略求解每一个子问题的最优…...
C++11特性:std::lock_guard是否会引起死锁?
今天在评审代码的时候,因为位于两个不同的线程中(一个是周期性事件线程,一个是触发式事件线程),需要对一个资源类的某些属性进行互斥的访问,因此采用lock_guard互斥量包装器,但是在升级的过程中…...

stm32使用定时器实现PWM与呼吸灯
PWM介绍 STM32F103C8T6 PWM 资源: 高级定时器( TIM1 ): 7 路 通用定时器( TIM2~TIM4 ):各 4 路 例如定时器2 PWM 输出模式: PWM 模式 1 :在 向上计数 时࿰…...

MAC本安装telnet
Linux运维工具-ywtool 目录 1.打开终端1.先安装brew命令2.写入环境变量4.安装telnet 1.打开终端 访达 - 应用程序(左侧) - 实用工具(右侧) - 终端 #注意:登入终端用普通用户,不要用MAC的root用户1.先安装brew命令 /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/H…...
[AIGC] 使用Spring Boot进行单元测试:一份指南
在现代软件开发过程中,确认你的应用正确运行是至关重要的一步。Spring Boot提供了一组实用工具和注解来辅助你在测试你的应用时,使得这个过程变得简单。下面就来分享一下如何在Spring Boot中进行单元测试。 文章目录 为什么需要单元测试Spring Boot单元测…...
使用 Go 语言统计 0-200000 的数字中,哪些是素数?
题目 使用 Go 语言统计 0-200000的数字中,哪些是素数? 思路 两种方法: 单循环遍历 1-200000 数字,并判断是否是素数。 使用了 Goroutine 和通道实现并发: 通过创建两个通道 intChan 和 primeChan,以及一…...

Fabric Measurement
Fabric Measurement 布料测量...

wayland(xdg_wm_base) + egl + opengles 使用 Assimp 加载材质文件Mtl 中的纹理图片最简实例(十六)
文章目录 前言一、3d 立方体 model 属性相关文件1. cube.obj2. cube.Mtl3. 纹理图片 cordeBouee4.jpg二、代码实例1. 依赖库和头文件1.1 assimp1.2 stb_image.h2. egl_wayland_obj_cube.cpp3. Matrix.h 和 Matrix.cpp4. xdg-shell-client-protocol.h 和 xdg-shell-protocol.c5.…...

面试常问:为什么 Vite 速度比 Webpack 快?
前言 最近作者在学习 webpack 相关的知识,之前一直对这个问题不是特别了解,甚至讲不出个123....,这个问题在面试中也是常见的,作者在学习的过程当中总结了以下几点,在这里分享给大家看一下,当然最重要的是…...

React腳手架已經創建好了,想使用Vite作為開發依賴
使用Vite作為開發依賴 安裝VITE配置VITE配置文件簡單的VITE配置項更改package.json中的scripts在根目錄中添加index.html現在可以瀏覽你的頁面了 安裝VITE 首先,在現有的React項目中安裝VITE npm install vite --save-dev || yarn add vite --dev配置VITE配置文件 …...
CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型
CVPR 2025 | MIMO:支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题:MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者:Yanyuan Chen, Dexuan Xu, Yu Hu…...
反向工程与模型迁移:打造未来商品详情API的可持续创新体系
在电商行业蓬勃发展的当下,商品详情API作为连接电商平台与开发者、商家及用户的关键纽带,其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息(如名称、价格、库存等)的获取与展示,已难以满足市场对个性化、智能…...

【WiFi帧结构】
文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成:MAC头部frame bodyFCS,其中MAC是固定格式的,frame body是可变长度。 MAC头部有frame control,duration,address1,address2,addre…...
Qt Widget类解析与代码注释
#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码,写上注释 当然可以!这段代码是 Qt …...

【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...

【从零学习JVM|第三篇】类的生命周期(高频面试题)
前言: 在Java编程中,类的生命周期是指类从被加载到内存中开始,到被卸载出内存为止的整个过程。了解类的生命周期对于理解Java程序的运行机制以及性能优化非常重要。本文会深入探寻类的生命周期,让读者对此有深刻印象。 目录 …...
C#学习第29天:表达式树(Expression Trees)
目录 什么是表达式树? 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持: 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...

宇树科技,改名了!
提到国内具身智能和机器人领域的代表企业,那宇树科技(Unitree)必须名列其榜。 最近,宇树科技的一项新变动消息在业界引发了不少关注和讨论,即: 宇树向其合作伙伴发布了一封公司名称变更函称,因…...
Bean 作用域有哪些?如何答出技术深度?
导语: Spring 面试绕不开 Bean 的作用域问题,这是面试官考察候选人对 Spring 框架理解深度的常见方式。本文将围绕“Spring 中的 Bean 作用域”展开,结合典型面试题及实战场景,帮你厘清重点,打破模板式回答,…...
Electron简介(附电子书学习资料)
一、什么是Electron? Electron 是一个由 GitHub 开发的 开源框架,允许开发者使用 Web技术(HTML、CSS、JavaScript) 构建跨平台的桌面应用程序(Windows、macOS、Linux)。它将 Chromium浏览器内核 和 Node.j…...