【Linux】多线程(互斥 同步)

我们在上一节多线程提到没有任何保护措施的抢票是会造成数据不一致的问题的。

那我们怎么办?
答案就是进行加锁。

目录

  • 加锁:
    • 认识锁和接口:
      • 初始化:
      • 加锁 && 解锁:
        • 全局的方式:
        • 局部的方式:
    • 原理角度理解:
    • 实现角度理解:
  • 同步:

加锁:

认识锁和接口:

初始化:

在这里插入图片描述
这个就是我们互斥锁的类型。其中互斥代表任何时刻只允许一个线程进行访问,锁代表为了实现互斥提供的一种方式。

锁不是一个单纯的内置类型,而是一个在这里插入图片描述
那么就肯定要对他进行初始化。
其中我们有两种初始化方式。
对于全局的锁,我们使用宏的方式。
在这里插入图片描述
而局部的锁我们则需要进行init初始化。

全局的所使用宏初始化后就不需要进行destory,因为随着生命周期的结束,会自动被系统回收。

但是局部的锁使用结束时要使用destory进行销毁。

加锁 && 解锁:

在这里插入图片描述
对于锁我们现在要有一个理解:对于多线程时,每个线程都会竞争这把锁,但只有一人会竞争成功,失败的会被阻塞,直到锁被解锁。

我们的锁是进行保护临界区的,或者说是保护临界资源。

而我们保护临界资源,本质是对临界区代码进行保护,怎么样理解这句话呢?

我们所有的资源都是通过代码进行访问的,所以本质上就是把访问资源的代码保护起来。

在这里插入图片描述
加锁之后当然要进行解锁在这里插入图片描述
所以我们就来改进一下上一章节产出的封装线程库 + 抢票的代码。

全局的方式:

我们要先看一下错误的加锁方式:
在这里插入图片描述
现象:
在这里插入图片描述
原因:因为只有一个线程会抢到锁,而对于上图程序而言一旦加锁就势必要把全部的票数抢完才可以解锁,也就意味着别的线程都无法抢票了。

所以上图的加锁方式是错误的,失去了多线程的意义。

正确的加锁:

在这里插入图片描述
现象:
在这里插入图片描述
在这里插入图片描述

结论:

  1. 加锁的范围要小粒度,非临界区是并行执行,临界区是串行执行,当你的粒度过大,串行的就多了,效率就低下了。
  2. 任何线程,进行抢票都需要申请锁,并不能因为程序是你写的而是个别线程出现特例。
  3. 所有的线程都申请锁,前提是所有的线程都可以看到这把锁,这意味着锁是共享资源,如何保证锁的安全?锁是原子的!
  4. 原子性:要么没做,要么就是做完,没有中间状态。他的反例就是吃饭,吃饭有没吃,也有吃了,但是还有吃饭中.
  5. 线程申请锁失败了就要被阻塞
  6. 线程申请锁成功继续运行
  7. 如果线程申请锁成功了,在执行临界区代码,在执行临界区代码期间可以被切换吗?
    答案是可以的,并且其他线程依旧无法进入,因为被切换的进程带着锁走了并没有释放!

结论:对于没有锁的线程,只有申请了锁的线程释放了线程才是有意义的。

其实对于访问临界区,对于无锁线程来说也是原子的。

局部的方式:

局部锁我们就要修改一下上节课的代码:
首先创建mythread时就不能单纯的只有name,还要再多一个mutex指针,于是我们选择传一个结构体指针即可。
在这里插入图片描述
在这里插入图片描述
具体变动如下,随着传入数据的修改也要修改一下连带的部分,造成了牵一发动全身的情形。
而引入了模板就可以避免这些问题,这就是模板的好处,但是由于这里我们并没有使用模板,所以暂时只能这样

#include <iostream>
#include <string>
#include <unistd.h>
#include <cstdio>
#include <pthread.h>class ThreadData
{
public:ThreadData(const std::string name, pthread_mutex_t* mutex) : _name(name), _mutex(mutex){}public:std::string _name;pthread_mutex_t* _mutex;
};
namespace cyc
{class mythread{public:typedef void (*func_t)(ThreadData*);mythread(ThreadData* td, func_t func) : _td(td), _func(func), _isRunning(false){}~mythread(){}void Excute(){_isRunning = true;_func(_td);_isRunning = false;}static void *routine(void *arg){mythread *self = static_cast<mythread *>(arg);self->Excute();return nullptr;}void Start(){int n = ::pthread_create(&_tid, nullptr, routine, (void *)this);if (n != 0){perror("pthread_create fail");exit(1);}}void Stop(){if (_isRunning){pthread_cancel(_tid);_isRunning = false;}}void Join(){int n = pthread_join(_tid, nullptr);if (n != 0){perror("pthread_join fail");exit(1);}std::cout << _td->_name << " Join sucess..." << std::endl;delete _td;}std::string GetStatus(){if (_isRunning)return "Running";elsereturn "sleeping";}private:ThreadData *_td;pthread_t _tid;func_t _func;bool _isRunning;};
}

传参时new一下
在这里插入图片描述
加锁时直接找td的成员即可。
在这里插入图片描述
此外我们还有另一种加锁方式,称之为RAII风格。
在这里插入图片描述
定义一个局部变量,当此时循环开始或者结束时自动创建,而正好这个类的构造与析构包含了lock与unlock,避免了繁琐的上下锁。
在这里插入图片描述

原理角度理解:

其实我们在加锁与解锁已经说明了很大一部分原理了。
在这里插入图片描述

接下来我们要探讨一下为什么lock后,
申请到锁的线程会继续执行程序,而其他线程会阻塞住?
最主要的原因就是申请到锁的线程在lock中会返回一个值,从而继续运行,而申请失败的线程则会不返回,线程就是阻塞了。

另外,我们的lock函数与pthread_mutex_t这个类型都是Pthread库中的,这个库会维护这套东西,当申请失败就会线程状态设置为S,放入等待队列中,当申请成功的线程unlock后,阻塞在lock函数内部的线程被重新唤醒,继续申请锁,重复以上步骤。

实现角度理解:

到实现层面上我们就必须谈谈原子性。

原子性在概念上是两态的,一条汇编就是原子的,他有多种体现形式,比如我们说过的抢票代码。

这里插个嘴,比较深的了解一下硬件,对整体节奏无影响:

我们的程序经过编译之后形成汇编,就像printf->十几行汇编->二进制:此时我们的二进制由两部分构成,一部分是二进制数据(int float…),一部分是二进制指令(被CPU进行执行)。
可是CPU怎么认识二进制指令?
CPU除了有寄存器构成,还有硬件电路构成,其中我们的数据寄存器中存储着数据,当我们进行加减乘除时怎么操作?CPU具有指令集,指令集可以知道执行什么操作,但具体怎么执行要靠硬件电路。
所以CPU在设计时就存在指令集这一概念。
因为CPU最初可以认识加减乘除,所以我们最开始的程序是由二进制编写的,比如纸带…–>但是效率太低,我们就有了汇编,也就有了编译器-------->C/C++。
这些语言都在指令集的基础上才得以存在。

为了实现互斥锁操作,大多数体系结构(CPU)都提供了swap或exchange指令,该指令的作用是把寄存器和内存单元的数据相交换,由于只有一条指令,保证了原子性,即使是多处理器平台,访问内存的 总线周期也有先后,一个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期。 现在我们把lock和unlock的伪代码看一下
在这里插入图片描述
现在我们将这个伪代码走一遍可以更好的感受锁(下图是一些辅助知识帮助理解)。

在这里插入图片描述

在这里插入图片描述
具体步骤:
在这里插入图片描述

同步:

我们可以观察到抢票的结果:其中一个线程抢票很频繁,这也是我们同步要解决的问题。

我们现在就要举例子进行一个形象的解释:

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

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

相关文章

【SkiaSharp绘图15】SKPath属性详解:边界、填充、凹凸、类型判断、坐标、路径类型

文章目录 SKPath 构造函数SKPath 属性Bounds 边界(宽边界)TightBounds紧边界FillType填充方式IsConcave 是否凹/ IsConvex 是否凸IsEmpty是否为空IsLine是否为线段IsRect是否为矩形IsOval是否为椭圆或圆IsRoundRect是否为圆角矩形Item[] 获取路径的坐标LastPoint最后点的坐标Po…

2024最全软件测试面试八股文(答案+文档+视频讲解)

Part1 1、你的测试职业发展是什么&#xff1f; 测试经验越多&#xff0c;测试能力越高。所以我的职业发展是需要时间积累的&#xff0c;一步步向着高级测试工程师奔去。而且我也有初步的职业规划&#xff0c;前3年积累测试经验&#xff0c;按如何做好测试工程师的要点去要求自…

OrangePi AIpro开发板测评 —— 相机图像获取

&#x1f482; 个人主页: 同学来啦&#x1f91f; 版权: 本文由【同学来啦】原创、在CSDN首发、需要转载请联系博主 &#x1f4ac; 如果文章对你有帮助&#xff0c;欢迎关注、点赞、收藏和订阅专栏哦 文章目录 &#x1f31f; 一、引言&#x1f31f; 二、OrangePi AIpro 简要介绍…

力扣206

题目 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1]示例 2&#xff1a; 输入&#xff1a;head [1,2] 输出&#xff1a;[2,1]示例 3&#xff1a; 输…

基于Transformer的端到端的目标检测 | 读论文

本文正在参加 人工智能创作者扶持计划 提及到计算机视觉的目标检测&#xff0c;我们一般会最先想到卷积神经网络&#xff08;CNN&#xff09;&#xff0c;因为这算是目标检测领域的开山之作了&#xff0c;在很长的一段时间里人们都折服于卷积神经网络在图像处理领域的优势&…

【数据库】E-R图、E-R模型到关系模式的转换、关系代数表达式、范式

一、E-R图 1、基本概念 2、实体集之间的联系 3、E-R图要点 &#xff08;1&#xff09;实体&#xff08;型&#xff09;的表示 &#xff08;2&#xff09;E-R图属性的表示 &#xff08;3&#xff09;联系的表示 4、E-R模型的例题 二、E-R模型到关系模式的转换 1、实体型的转换…

使用getline()从文件中读取一行字符串

我们知道&#xff0c;getline() 方法定义在 istream 类中&#xff0c;而 fstream 和 ifstream 类继承自 istream 类&#xff0c;因此 fstream 和 ifstream 的类对象可以调用 getline() 成员方法。 当文件流对象调用 getline() 方法时&#xff0c;该方法的功能就变成了从指定文件…

基于STM32F103C8T6的同步电机驱动-CubeMX配置与IQmath调用

基于STM32F103C8T6的同步电机驱动-CubeMX配置与IQmath调用 一、功能描述: 上位机通过CAN总线实现对电机的运动控制,主要包含三种模式:位置模式、速度模式以及力矩模式。驱动器硬件核心为STM32F103C8T6,带相电压采集电路以及母线电压采集电路。其中供电电压12V。 PWM中心对…

【单片机毕业设计选题24047】-基于阿里云的工地环境监测系统

系统功能: 基于STM32完成 主机&#xff08;阿里云以及oled屏显示位置一&#xff09;&#xff1a;烟雾检测&#xff0c;温湿度检测&#xff0c;噪声检测&#xff0c;且用OLED屏显示&#xff0c;设置阈值&#xff0c;超过报警&#xff08;蜂鸣器&#xff09;。 从机&#xff0…

LeetCode题练习与总结:对链表进行插入排序--147

一、题目描述 给定单个链表的头 head &#xff0c;使用 插入排序 对链表进行排序&#xff0c;并返回 排序后链表的头 。 插入排序 算法的步骤: 插入排序是迭代的&#xff0c;每次只移动一个元素&#xff0c;直到所有元素可以形成一个有序的输出列表。每次迭代中&#xff0c;…

Element中的日期时间选择器DateTimePicker和级联选择器Cascader

简述&#xff1a;在Element UI框架中&#xff0c;Cascader&#xff08;级联选择器&#xff09;和DateTimePicker&#xff08;日期时间选择器&#xff09;是两个非常实用且常用的组件&#xff0c;它们分别用于日期选择和多层级选择&#xff0c;提供了丰富的交互体验和便捷的数据…

【server】nacos 安装

1、本地安装 1.1 nacos官网 Nacos官网| Nacos 配置中心 | Nacos 下载| Nacos 官方社区 | Nacos 官网 git 下载地址&#xff1a;https://github.com/alibaba/nacos/releases 1.2 解压并修改配置 1.2.1 通过properties 修改配置&#xff0c;添加数据库配置 1.2.2 创建数据库&…

字节码编程ASM之生成变量并sout

写在前面 本文看下如何通过asm生成变量并sout。 1&#xff1a;代码 直接看代码吧&#xff0c;注释很详细&#xff0c;有不懂的&#xff0c;留言告诉我&#xff1a; package com.dahuyuo.asmtest;import org.objectweb.asm.*; import org.objectweb.asm.commons.AdviceAdapt…

VCS+Vivado联合仿真BUG

场景&#xff1a; 在vcsvivado联合仿真过程中&#xff0c;对vivado导出的shell脚本修改&#xff0c;修改某些source文件路径&#xff0c;vcs编译时会报Permission Denied。 问题描述 对shell脚本修改如下&#xff1a; 修改仅为注释掉某一行&#xff0c;下面变为source文件新…

Linux shell编程学习笔记62: top命令 linux下的任务管理器

0 前言 top命令是Unix 和 Linux下常用的性能分析工具&#xff0c;提供了一个动态的、交互式的实时视图&#xff0c;显示系统的整体性能信息&#xff0c;以及正在运行的进程的相关信息&#xff0c;包括各个进程的资源占用状况&#xff0c;类似于Windows的任务管理器。 1 top命令…

数据特征采样在 MySQL 同步一致性校验中的实践

作者&#xff1a;vivo 互联网存储研发团队 - Shang Yongxing 本文介绍了当前DTS应用中&#xff0c;MySQL数据同步使用到的数据一致性校验工具&#xff0c;并对它的实现思路进行分享。 一、背景 在 MySQL 的使用过程中&#xff0c;经常会因为如集群拆分、数据传输、数据聚合等…

【堆 优先队列】23. 合并 K 个升序链表

本文涉及知识点 堆 优先队列 LeetCode23. 合并 K 个升序链表 给你一个链表数组&#xff0c;每个链表都已经按升序排列。 请你将所有链表合并到一个升序链表中&#xff0c;返回合并后的链表。 示例 1&#xff1a; 输入&#xff1a;lists [[1,4,5],[1,3,4],[2,6]] 输出&#…

基流科技:超算界的新星,Pre-A轮融资大获成功

基流科技:超算界的新星,Pre-A轮融资大获成功! 在科技的浪潮中,一颗新星正在冉冉升起——基流科技,一家开放算力网络提供商,以其革命性的技术在超算界引起了轰动。今年年初,基流科技完成了 Pre-A 轮融资,由光速光合领投,此前已获得奇绩创坛、微梦传媒等知名投资方的青…

mysql定时备份数据库

文章目录 核心目标思路具体方法一、编写脚本二、修改文件属性三、找一个mysqldump文件四、把.sh放到定时器里 其它&#xff1a;windows的脚本 核心目标 解决数据库定时备份的工作。centos环境。 思路 用centos的crontab定时执行脚本。 具体方法 一、编写脚本 编写backup_…

Kafka集群部署(手把手部署图文详细版)

1.1.1 部署zookpeer 在node02下载并解压zookeeper软件包 cd /usr/local wget https://archive.apache.org/dist/zookeeper/zookeeper-3.4.6/zookeeper-3.4.6.tar.gz 或者&#xff1a;scp cat192.168.28.100:/home/cat/zookeeper-3.4.6.tar.gz /tmp&#xff08;注意目录&#xf…