【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";
            else
                return "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的伪代码看一下
在这里插入图片描述
现在我们将这个伪代码走一遍可以更好的感受锁(下图是一些辅助知识帮助理解)。

在这里插入图片描述

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

同步:

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

go 学习 之 HTTP微服务示例

1. 背景 学习ing 2. 创建文件&#xff1a;server.go go package mainimport ("github.com/gogf/gf/contrib/registry/file/v2""github.com/gogf/gf/v2/frame/g""github.com/gogf/gf/v2/net/ghttp""github.com/gogf/gf/v2/net/gsvc"&…

Mac 运行 Windows 软件,Parallels Desktop 19和 CrossOver 24全面对比

Parallels Desktop 和 CrossOver 都是能满足你「在 Mac 上运行 Windows 软件」需求的工具。可能很多人都已经知道 Parallels Desktop 是「虚拟机」&#xff0c;但 CrossOver 其实并不是「虚拟机」。这两款软件有相同的作用&#xff0c;但由于实现原理的不同&#xff0c;两者也有…

在线签约如何选择?2024年10款顶级app大比拼

支持电子合同签约的10大app&#xff1a;e签宝、上上签、DocuSign、契约锁、Adobe Sign、法大大、SignNow、安心签、HelloSign、PandaDoc。 无论是企业之间的交易还是个人服务合同&#xff0c;线上电子合同签约提供了一种便捷、高效且安全的方式来处理法律文档。本文将介绍几款优…

django学习入门系列之第四点《案例 博客案例》

文章目录 container面板案例 博客案例往期回顾 container 堆叠到两边 <div class"container-fluid clearfix"><div class"col-sm-9">1</div><div class"col-sm-3">2</div> </div>放在中间 <div clas…

-bash: /snap/bin/docker: 没有那个文件或目录

-bash: /snap/bin/docker: 没有那个文件或目录 解决办法 export PATH$PATH:/usr/bin/docker然后&#xff0c;重新加载配置文件 source ~/.bashrc

【BUUCTF-PWN】7-[第五空间2019 决赛]PWN5

参考&#xff1a;BUU pwn [第五空间2019 决赛]PWN5 //格式化字符串漏洞 - Nemuzuki - 博客园 (cnblogs.com) 格式化字符串漏洞原理详解_printf 任意内存读取-CSDN博客 32位小端排序&#xff0c;有栈溢出保护 运行效果&#xff1a; 查看main函数 存在格式化字符串漏洞 输…

深度学习1

1.支持向量机Support Vector Machine&#xff08;SVM&#xff09;是一种对数据二分类的线性分类器&#xff0c;目的是寻找一个超平面对样本进行分割&#xff0c;广泛应用人像识别&#xff0c;手写数字识别&#xff0c;生物信息识别。 二维空间分割界是一条直线&#xff0c;在三…

校园失物招领系统带万字文档java项目失物招领管理系统java课程设计java毕业设计springboot vue

文章目录 校园失物招领系统一、项目演示二、项目介绍三、万字字项目文档四、部分功能截图五、部分代码展示六、底部获取项目源码带万字文档&#xff08;9.9&#xffe5;带走&#xff09; 校园失物招领系统 一、项目演示 校园失物招领系统 二、项目介绍 语言: Java 数据库&…

利用数据集,用机器学习模型对股市预测,聊聊看!

&#x1f3c6;本文收录于「Bug调优」专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收藏&&…

[C++][CMake][CMake基础]详细讲解

目录 1.CMake简介2.大小写&#xff1f;3.注释1.注释行2.注释块 4.日志 1.CMake简介 CMake是一个项目构建工具&#xff0c;并且是跨平台的 问题 – 解决 如果自己动手写Makefile&#xff0c;会发现&#xff0c;Makefile通常依赖于当前的编译平台&#xff0c;而且编写Makefile的…

【Pyhton】读取寄存器数据到MySQL数据库

目录 步骤 modsim32软件配置 Navicat for MySQL 代码实现 步骤 安装必要的库&#xff1a;确保安装了pymodbus和pymysql。 配置Modbus连接&#xff1a;设置Modbus从站的IP地址、端口&#xff08;对于TCP&#xff09;或串行通信参数&#xff08;对于RTU&#xff09;。 连接M…

人工智能对网络安全有何影响?

人工智能网络安全在短期、中期和长期如何变化 当今数字时代网络安全的重要性 在谈论人工智能在网络安全中的作用时&#xff0c;必须首先考虑短期影响&#xff0c;因为它们是最明显的&#xff0c;而且它是一个未知的领域&#xff0c;需要超越直接炒作的能力。 因此&#xff0…

叠加的正弦波逼近一个矩形波

叠加的正弦波逼近一个矩形波 flyfish 依次绘制1个、2个、4个和10个正弦波的叠加效果。每个正弦波的频率是基频的奇数倍&#xff0c;且幅度逐渐减小。最终&#xff0c;这些叠加的正弦波将逼近一个矩形波。 import numpy as np import matplotlib.pyplot as plt plt.rcParams[f…

免费办公软件 -- LibreOffice v24.2.4

软件简介 LibreOffice是一款免费且开源的办公软件套件&#xff0c;它为个人和商业用户提供了一套完整的工具&#xff0c;用于处理文本文档、电子表格、演示文稿和其他类型的文件。这款软件兼容 Microsoft Office 的文件格式&#xff0c;如 .docx、.xlsx 和 .pptx&#xff0c;使…

数据结构(3.8)——栈的应用

栈在括号匹配中的应用 流程图 代码 #include <stdio.h> #include <stdlib.h> #define MaxSize 10typedef struct {char data[MaxSize];int top; } SqStack;// 初始化栈 void InitStack(SqStack* S) {S->top -1; // 初始化栈顶指针 }// 判空 bool StackEmpty(…

模拟退火算法4—应用

TSP&#xff08;旅行商&#xff09;问题是最有代表性的优化组合问题之一&#xff0c;其应用已逐步渗透到各个技术领域和我们的日常生活中.它一开始是为交通运输而提出的&#xff0c;比如飞机航线安排、送邮件、快递服务、设计校车行进路线等等.实际上其应用范围扩展到了许多其他…

SLAM 精度评估

SLAM 精度的评估有两个最重要的指标&#xff0c;即绝对轨迹误差&#xff08;ATE&#xff09;和相对位姿误差&#xff08;RPE&#xff09;的 均方根误差&#xff08;RMSE&#xff09;: 绝对轨迹误差:直接计算相机位姿的真实值与 SLAM 系统的估计值之间的差值&#xff0c;首先将…

力扣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; 输…

flask的进阶使用方法

【 一 】一对多关系 # 1 一对一 [本质就是一对多--》多的那个唯一] # 2 一对多 # 3 多对多1.1 关系 #### 一对多关系 class Hobby(Base):__tablename__ hobbyid Column(Integer, primary_keyTrue)caption Column(String(50), default篮球)def __str__(self):return sel…

vue中一周的时间选择多个阶段(手动表格选择)

先给大家看一下效果图 源代码 <template><div style"width: 45%"><div style"width: 100%"><div class"time"><div class"timeleft">星期/时间</div><div class"timeright"><…