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; // 成员常量
};

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

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

相关文章

《边缘计算:连接未来的智慧之桥》

随着物联网、5G等技术的快速发展&#xff0c;边缘计算作为一种新兴的计算模式&#xff0c;正逐渐引起人们的广泛关注。边缘计算通过将数据处理和存储功能放置在距离数据产生源头更近的位置&#xff0c;实现了更快速、更可靠的数据处理和交换&#xff0c;为各行各业带来了前所未…

【项目设计】基于MVC的负载均衡式的在线OJ

项目代码&#xff08;可直接下载运行&#xff09; 一、项目的相关背景 学习编程的小伙伴&#xff0c;大家对力扣、牛客或其他在线编程的网站一定都不陌生&#xff0c;这些编程网站除了提供了在线编程&#xff0c;还有其他的一些功能。我们这个项目只是做出能够在线编程的功能。…

详解JavaScript中this指向

this 原理 this 是一个指针型变量&#xff0c;它指向当前函数的运行环境。 1.内存的数据结构 var obj { foo: 5 };2.函数 var obj { foo: function () {} };引擎会将函数单独保存在内存中&#xff0c;然后再将函数的地址赋值给foo属性的value属性。 由于函数是一个单独的…

代码随想录day28(1)二叉树:二叉搜索树中的插入操作(leetcode701)

题目要求&#xff1a;给定二叉搜索树&#xff08;BST&#xff09;的根节点和要插入树中的值&#xff0c;将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据保证&#xff0c;新值和原始二叉搜索树中的任意节点值都不同。 思路&#xff1a;对于二叉搜索树来说&…

常见技术难点及方案

1. 分布式锁 1.1 难点 1.1.1 锁延期 同一时间内不允许多个客户端同时获得锁&#xff1b; 1.1.2 防止死锁 需要确保在任何故障场景下&#xff0c;都不会出现死锁&#xff1b; 1.2.3 可重入 特殊的锁机制&#xff0c;它允许同一个线程多次获取同一个锁而不会被阻塞。 1.2…

Web前端Html的表单

表单的关键字&#xff1a; form标签表示一个表单区域 action“后端地址” method“提交数据方式:get/post” input 单行输入框 type“text” 文本 name“定义名称 名字自定义” 向后端提交的键 readonly“readonly” 只读&#xff0c;不可修改&#xff0c;但是可以提交 disab…

C语言学习-day22-函数递归1

程序调用自身的编程技巧被称为递归。举个例子&#xff1a; int main() { printf("hehe\n"); main(); return 0; } 比如这种&#xff0c;自己调用自己后每次都打印一个hehe。 递归的核心思考方式在于&#xff1a;大事化小。 做个练习&#xff1a;接收一个整型值&a…

鸿蒙Harmony应用开发—ArkTS-类型定义

说明&#xff1a; 本模块首批接口从API version 7开始支持&#xff0c;后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。 Resource 资源引用类型&#xff0c;用于设置组件属性的值。 可以通过$r或者$rawfile创建Resource类型对象&#xff0c;不可以修改Res…

【S056】Clause46--XGMII接口摘要

文章目录 XGMII数据与lane的对应关系是什么&#xff1f;XGMII上的数据流格式是什么&#xff1f;如何保证0xFB每次都在Lane0&#xff1f;方法一&#xff1a;方法二&#xff1a; Link Fault信号 XGMII数据与lane的对应关系是什么&#xff1f; XGMII上的数据流格式是什么&#xff…

GDC期间LayaAir启动全球化战略

3 月 18 日至 3 月 22 日&#xff0c;一年一度的游戏开发者大会&#xff08;GDC&#xff09;在美国旧金山举行。在此期间&#xff0c;Layabox宣布LayaAir引擎启动全球扩张战略&#xff0c;这标志着引擎将步入快速发展的新阶段。此举旨在利用公司先进的3D引擎技术&#xff0c;将…

Linux之git

一、什么叫做版本控制 版本控制&#xff08;Revision control&#xff09;是一种在开发的过程中用于管理我们对文件、目录或工程等内容的修改历史&#xff0c;方便查看更改历史记录&#xff0c;备份以便恢复以前的版本的软件工程技术。简单来说就是用于管理多人协同开发项目的技…

Affiliate Stores: 建立营销联盟商店的详细教程- US Domain Center主机

第一步&#xff1a;了解营销联盟商店 营销联盟商店是一种电子商务模式&#xff0c;您可以在其中通过推广其他企业的产品或服务来赚取佣金。您在自己的网站上展示其他企业的产品&#xff0c;并在买家购买时获得佣金。通过 WooCommerce 平台&#xff0c;您可以轻松创建一个营销联…

学习总结!

java目前学习到了数组部分 广搜&#xff0c;在开始的时候把#处理一下会好做很多&#xff0c;遇到上下两层都是# 的&#xff0c;就把上下两层的这个位置都弄成墙就行。还有遇到 一层是#&#xff0c;一层是墙的。也直接把俩都弄城墙就行&#xff0c;省的要判断他撞死&#xff0c;…

部署mysql,前端,后端

部署mysql docker pull mysql 从镜像源中拉取镜像。 创建mysql容器 docker run -d \--name mysql_container \-p 3306:3306 \-e TZAsia/Shanghai \-e MYSQL_ROOT_PASSWORD123 \--restartalways \-v /opt/mysql:/var/lib/mysql \mysql -d后台运行&#xff0c;--name指定容器…

【JavaScript】JavaScript 程序流程控制 ⑤ ( 嵌套 for 循环 | 嵌套 for 循环概念 | 嵌套 for 循环语法结构 )

文章目录 一、嵌套 for 循环1、嵌套 for 循环概念2、嵌套 for 循环语法结构 二、嵌套 for 循环案例1、打印三角形2、打印乘法表 一、嵌套 for 循环 1、嵌套 for 循环概念 嵌套 for 循环 是一个 嵌套的 循环结构 , 其中一个 for 循环 位于另一个 for 循环的内部 , 分别是 外层 f…

计算机网络实验——学习记录二(HTTP协议)

1. Linux主机上连接互联网的网络接口是&#xff1a;ens33。 2. 在显示过滤器&#xff08;Filter&#xff09;中输入“ http.host www.zzu.edu.cn”&#xff0c;筛选出HTTP协议报文首部行中包含“ Host&#xff1a;www.zzu.edu.cn”的报文&#xff08;目的地址是Web服务器的报…

学习或复习电路的game推荐:nandgame(NAND与非门游戏)、Turing_Complete(图灵完备)

https://www.nandgame.com/ 免费 https://store.steampowered.com/app/1444480/Turing_Complete/ 收费&#xff0c;70元。据说可以导出 Verilog &#xff01;

深度学习pytorch——可视化visdom(持续更新)

安装可看&#xff1a;e: Error while finding module specification for ‘visdom.server‘ (ModuleNotFoundError: No module name-CSDN博客 在命令行窗口使用python -m visdom.server&#xff0c;会出现一个web地址&#xff0c;在浏览器中访问&#xff0c;即可看见在python中…

springboot做自定义校验注解

目录 自定义校验注解的实现 注意&#xff1a; 首先&#xff0c;我们需要自定义一个校验注解&#xff1a; 注解含义&#xff1a; Target({ElementType.FIELD}) Retention(RetentionPolicy.RUNTIME) Constraint(validatedBy PhoneValidator.class) 校验注解逻辑实现类&a…

算法打卡day24|回溯法篇04|Leetcode 93.复原IP地址、78.子集、90.子集II

算法题 Leetcode 93.复原IP地址 题目链接:93.复原IP地址 大佬视频讲解&#xff1a;复原IP地址视频讲解 个人思路 这道题和昨天的分割回文串有点类似&#xff0c;但这里是限制了只能分割3次以及分割块的数字大小&#xff0c;根据这些不同的条件用回溯法解决就好啦 解法 回溯…