【Linux】线程互斥

文章目录

    • 1. 背景概念
      • 多个线程对全局变量做-- 操作
    • 2. 证明全局变量做修改时,在多线程并发访问会出问题
    • 3. 锁的使用
      • pthread_mutex_init
      • pthread_metux_destroy
      • pthread_mutex_lock 与 pthread_mutex_unlock
      • 具体操作实现
        • 设置为全局锁
      • 设置为局部锁
    • 4. 互斥锁细节问题
    • 5. 互斥锁的原理
      • 背景知识
      • 具体实现

1. 背景概念

多线程中,存在一个全局变量,是被所有执行流共享的
根据历史经验,线程中大部分资源都会直接或者间接共享
只要存在共享,就可能存在被并发访问的问题


假设有一间教室被学校内的所有社团共享的,所以这个教室属于公共资源,
有可能当一个社团在这个教室举办活动时,别的社团也想占用这个教室
即 一个公共资源被并发访问了
为了保证访问时不能被别人去抢走,所以就把门窗都关上,直到访问完,才让别人进来
即 发生互斥


为了保证对应的共享资源的安全,用某种方式将共享资源保护起来,这部分共享资源称之为临界资源

访问临界资源执行的代码 称之为 临界区

多个线程对全局变量做-- 操作

假设有一个全局变量 g_val=100
有两个 线程A 和 线程B,分别对同一个全局变量g_val进行–操作


第一步g_val变量要修改,要把内存的数据load到寄存器中
第二步在寄存器内部,进行数据的–操作
第三步把在寄存器中修改后的数据写回到内存中

g_val–,在C语言上是一条语句,但实际上至少要有三条语句


线程A执行g_val-- 操作

第1步把数据load到寄存器中,第2步在寄存器中对数据做–操作
线程A正准备做第3步时,时间片到了,线程A不能继续向后运行了
线程A要把自己的上下文保护起来,并且将寄存器中的数据也带走了


线程a认为值已经被改成99了,并且还有第三条语句还没有执行


线程B执行 g_val-- 操作
第1步把数据load到寄存器中,
线程B认为g_val没有被写过,所以g_val依旧从100开始修改
第2步在寄存器中对数据做–操作
第3步把修改后的数据写回内存中,即将内存中g_val从100改成99


假设线程B通过while无线循环,则把g_val修改了90次后,g_val值变为10,
此时再次执行时间片到了,所以无法执行第3步,把线程B的上下文保存起来


此时再次执行线程A,由于上次执行线程A时第3步没有执行,所以线程A继续执行第3步
但是内存中的g_val为上次线程B修改后的值10,又被改为99了
把线程B做的数据修改干掉了


对全局变量做–,没有保护的话,会存在并发访问的问题,进而导致数据不一致
g_val被称为 共享资源, 对共享资源进行一定的保护即 临界资源 用来衡量共享资源的
任何一个线程 都有自己的代码访问临界资源,这部分代码 被称为 临界区
同样存在不访问临界资源的区域 被称为 非临界区
用于 衡量 线程代码的

让多个线程安全的访问临界资源 —— 加锁 即完成互斥访问

把三条指令,看起来就像一条指令 被称为 原子性 (要么就不执行,要执行就都执行)

2. 证明全局变量做修改时,在多线程并发访问会出问题

创建一个全局变量 tickets 作为票数,并创建4个线程,
分别调用自定义函数 thread_run 来对tickets进行–操作 ,直到tickets的值<0才结束


创建一个全局变量 tickets 作为票数,并创建4个线程,
分别调用自定义tickets变为负数 ,是不合理的


在我们设计中,若ticjets<0就会直接break退出,只有当tickets>0时才会打印出对应tickets的值


假设 tickets==1 ,此时有 a b c d 4个线程
当线程a 通过判断 进入 if语句中的 sleep中时 ,被上下文保护了
线程b 也执行判断 进入 if语句,继续向下执行完 tickets-- ,
此时的tickets的值为0,CPU就会再次执行还未执行完的线程a 的剩余步骤,tickets-- 即 0-1 =-1


3. 锁的使用

为了避免全局变量 出现负数的情况,所以引入 加锁 用于保证共享资源的安全

pthread_mutex_init

输入 man pthread_mutex_init

第一个参数 为 互斥锁,对该锁进行初始化,初始化该锁处于工作状态
第二个参数 为属性 一般设置为 nullptr


一般有两种初始化方案

第一种,锁为全局变量 ,直接用PTHREAD_MUTEX_INITIALIZER,对锁进行初始化
后面就不用 通过pthread_mutex_destroy 对其进行摧毁


第二种,若锁为局部变量,就必须调用pthread_init 进行初始化,用完后也必须调用 pthread_destroy 进行销毁

pthread_metux_destroy

在这里插入图片描述
参数为锁
对锁进行销毁

若锁为局部变量
则需要在创建线程之前初始化,使用完线程后在销毁

pthread_mutex_lock 与 pthread_mutex_unlock


输入 man pthread_mutex_lock 加锁

参数为 锁
对该锁进行加锁
若加锁成功就会进入临界区中访问临界区代码
若加锁失败,就会把当前执行流阻塞


输入 man pthread_mutex_unlock 解锁

对该锁进行解锁

具体操作实现

设置为全局锁

若锁为全局变量,可以选择在主函数中初始化锁 与销毁锁


使用 锁 ,进行加锁操作 ,保证共享资源的安全


执行可执行程序后,,发现tickets的值没有负数存在

设置为局部锁

锁要被所有线程看到

所以要定义一个类 TData 包含线程的名字 互斥锁对应的指针
表示线程创建时,要被传的参数


在主函数内部,通过 TData 类型new一个对象td,将公共的锁传递给所有线程
将对象td传递给自定义函数,作为参数args


在自定义函数上,通过对 对象内部的_pmutex的操作 完成加锁与解锁
通过访问对象内部的_name,来调用对应线程的名字


执行可执行程序符合预期,没有出现负数

4. 互斥锁细节问题

1. 访问同一个临界资源的线程,都要进行加锁操作保护,而且必须加同一把锁
(每一个线程在访问临界资源之前都要先加锁)

2. 每一个线程访问临界区之前,得加锁,加锁本质是给临界区加锁
加锁粒度尽量要细一些

3. 线程访问临界区的时候,需要先加锁 -> 所有线程都必须要先看到同一把锁 -> 锁本身就是公共资源
->锁如何保证自身安全? ->加锁和解锁本身就是原子的
(原子性:要么就不加锁,要加锁就加成功)
锁的申请是安全的,就可以保证锁保护的资源本身也是安全的

4. 临界区可以是一行代码,也可以是一批代码
访问全局资源时,可能会存在多并发访问的问题


切换会有影响吗?
加锁在临界区内,加锁后,对临界区代码进行任意切换会不会影响数据出现安全方面的问题?
不会,我不在期间,其他人没有办法进入临界区,因为无法成功申请到锁,锁被我拿走了


存在一个VIP自习室,一次只能有一个人
这个自习室有一个特点,无人值班,门旁边有一把钥匙,门默认是锁着的

若小明想要到这个自习室进行自习,就需要拿到钥匙,把门打开 ,才可以使用自习室
当小明进来后,为了防止别人打扰,把门进行反锁,同时钥匙在小明口袋中
其他人是没办法进来 这个门被反锁的自习室

突然在自习室内的小明 想去上厕所,但是他还想继续自习
所以去上厕所之前,把门又从外面锁上了,把钥匙再次装入口袋中
上厕所期间,并不担心有人进入自习室,因为被锁住了


申请锁后,相当于把锁拿到自己手上了,同时其他人就无法申请了

当访问临界区时,有可能被挂起被阻塞,但是并不担心别人进入临界区中 此时并没有解锁,没有归还锁,
即便当前线程不在, 其他线程也无法调度

5. 互斥锁的原理

背景知识

1.为了实现互斥锁,大多数体系结构(CPU)提供了 汇编指令 即 swap或exchange指令
指令作用为 把寄存器和内存单元的数据相交换


将CPU中的数据与 内存中的数据进行交换
按照传统做法,一条汇编做不到,所以需要借助 一个临时空间进行保存,然后才能进行交换
体系结构为了支持锁的实现,提供了 swap /exchange 指令
一条汇编,把 CPU的数据与 内存中的数据做交换

只有一条汇编指令,保证了原子性


2.寄存器的硬件只有一套,但是寄存器内部的数据是每一个线程都要有的
寄存器 != 寄存器内容(执行流的上下文)

具体实现

用互斥锁这样的类型定义变量,在内存里开辟空间
默认mutex等于1

以线程为单位,调用这部分加锁的代码
并不是线程自己去调,而是要让CPU去跑,CPU会去执行线程的代码

CPU上有一个寄存器,其被命名为 %al
假设 有线程a (thread a) 和线程b (thread b),都要执行加锁的任务


执行加锁对应的伪代码的第一个指令, 即先把0放入寄存器中


所以当线程a把数据放入寄存器中,这个数据依旧属于线程a的上下文


第一条指令 本质为 调用线程,向自己的上下文写入0


第二条指令,将cpu的寄存器中的%al 与 内存中的mutex 进行交换
交换的本质是 :将共享数据交换到 自己的私有的上下文中
所有线程看到的是同一把锁,mutex作为共享数据 ,交换到寄存器的上下文中,寄存器作为线程的私有上下文 即 加锁
数据1 就可以被看作是锁

交换 只有 一条汇编指令 ,要么没交换,要不就交换完了 即加锁的原子性



判断al寄存器中的内容是否大于0,
若大于0,返回0,代表加锁成功

假设线程a 即将执行对于判断时 ,进行线程切换,
此时线程a 要带走自己的上下文 即 al寄存器的值为1 ,同时记录下即将执行判断


切换成线程b,继续执行前两条指令 ,先将 al寄存器数据置为0
再将寄存器中的数据 与 内存中的数据 进行 交换


线程b 继续执行时 要进行判断 ,寄存器数据不大于0,当前线程被挂起
线程b申请锁失败
线程b 带走了自己的上下文 即 寄存器中的数据为0


再次切换成 线程a,带回来线程a的寄存器数据 1,并继续执行 上次还未执行到的判断


线程a的寄存器中的数据大于0,返回0,申请锁成功

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

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

相关文章

DevOps该怎么做?

年初在家待了一段时间看了两本书收获还是挺多的. 这些年一直忙于项目, 经历了软件项目的每个阶段, 多多少少知道每个阶段是个什么, 会做哪些事情浮于表面, 没有深入去思考每个阶段背后的理论基础, 最佳实践和落地工具. 某天leader说你书看完了, 只有笔记没有总结, 你就写个总结…

ifconfig工具与驱动交互解析(ioctl)

Linux ifconfig&#xff08;network interfaces configuring&#xff09; Linux ifconfig命令用于显示或设置网络设备。ifconfig可设置网络设备的状态&#xff0c;或是显示目前的设置。同netstat一样&#xff0c;ifconfig源码也位于net-tools中。源码位于net-tools工具包中&am…

【LeetCode】HOT 100(2)

题单介绍&#xff1a; 精选 100 道力扣&#xff08;LeetCode&#xff09;上最热门的题目&#xff0c;适合初识算法与数据结构的新手和想要在短时间内高效提升的人&#xff0c;熟练掌握这 100 道题&#xff0c;你就已经具备了在代码世界通行的基本能力。 目录 题单介绍&#…

软件测试测试环境搭建很难?一天学会这份测试环境搭建教程

如何搭建测试环境&#xff1f;这既是一道高频面试题&#xff0c;又是困扰很多小伙伴的难题。因为你在网上找到的大多数教程&#xff0c;乃至在一些培训机构的课程&#xff0c;都不会有详细的说明。 你能找到的大多数项目&#xff0c;是在本机电脑环境搭建环境&#xff0c;或是…

【单目标优化算法】孔雀优化算法(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

MySQL — 主从复制介绍

文章目录 主从复制一、概述二、原理三、 搭建主从复制结构3.1 服务器准备3.2 主库配置3.3 从库配置 主从复制 一、概述 ​ 主从复制是指将主数据库的DDL和DML操作通过二进制日志传到从库服务器中&#xff0c;然后在从库上对这些日志重新执行&#xff08;也叫重做&#xff09;…

1. TensorRT量化的定义及意义

前言 手写AI推出的全新TensorRT模型量化课程&#xff0c;链接&#xff1a;TensorRT下的模型量化。 课程大纲如下&#xff1a; 1. 量化的定义及意义 1.1 什么是量化&#xff1f; 定义 量化(Quantization)是指将高精度浮点数(如float32)表示为低精度整数(如int8)的过程&…

如何运用R语言进行Meta分析在【文献计量分析、贝叶斯、机器学习等】多技术的融合

Meta分析是针对某一科研问题&#xff0c;根据明确的搜索策略、选择筛选文献标准、采用严格的评价方法&#xff0c;对来源不同的研究成果进行收集、合并及定量统计分析的方法&#xff0c;最早出现于“循证医学”&#xff0c;现已广泛应用于农林生态&#xff0c;资源环境等方面。…

华为OD机试真题 Java 实现【高矮个子排队】【2023Q2 100分】,附详细解题思路

一、题目描述 现在有一队小朋友&#xff0c;他们高矮不同&#xff0c;我们以正整数数组表示这一队小朋友的身高&#xff0c;如数组{5,3,1,2,3}。 我们现在希望小朋友排队&#xff0c;以“高”“矮”“高”“矮”顺序排列&#xff0c;每一个“高”位置的小朋友要比相邻的位置高…

【配电网重构】基于改进二进制粒子群算法的配电网重构研究(Matlab代码实现

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

批量剪辑视频工具源码开发搭建分享

搭建步骤 1. 首先需要根据自身产品确定视频类型及需要实现的视频效果 2. 根据预期视频效果选择视频上传模式&#xff0c;并将视频素材进行上传 3. 添加音频、字幕&#xff0c;标题等与素材进行组合。 4. 设置投放计划&#xff0c;包括&#xff1a;视频标题、视频话题等 5.…

dom阶段实战内容

window定时器方法 ◼ 目前有两种方式可以实现&#xff1a;  setTimeout 允许我们将函数推迟到一段时间间隔之后再执行。  setInterval 允许我们重复运行一个函数&#xff0c;从一段时间间隔之后开始运行&#xff0c;之后以该时间间隔连续重复运行该函数。 ◼ 并且通常情况…

周赛347(模拟、思维题、动态规划+优化)

文章目录 周赛347[2710. 移除字符串中的尾随零](https://leetcode.cn/problems/remove-trailing-zeros-from-a-string/)模拟 [2711. 对角线上不同值的数量差](https://leetcode.cn/problems/difference-of-number-of-distinct-values-on-diagonals/)模拟 [2712. 使所有字符相等…

什么是分布式事务?

什么是分布式事务&#xff1f; 分布式对应的是单体架构&#xff0c;互联网早起单体架构是非常流行的&#xff0c;好像是一个家族企业&#xff0c;大家在一个家里劳作&#xff0c;单体架构如下图&#xff1a; 但是随着业务的复杂度提高&#xff0c;大家族人手不够&#xff0c;…

如何使用 Python Nornir 实现基于 CLI 的网络自动化?

在现代网络环境中&#xff0c;网络自动化已成为管理和配置网络设备的重要工具。Python Nornir 是一个强大的自动化框架&#xff0c;它提供了一个简单而灵活的方式来执行网络自动化任务。本文将详细介绍如何使用 Python Nornir 实现基于 CLI 的网络自动化。 1. Python Nornir 概…

view的常用属性和方法介绍(arcgis for javascript)

ArcGIS for JavaScript中的视图&#xff08;view&#xff09;是一个地图实例类&#xff0c;用于管理地图的显示区域、符号和标注等。通过视图类&#xff0c;可以实现以下功能&#xff1a; 显示地图&#xff1a;将地图显示在Web页面上。 缩放&#xff1a;缩放视图到指定的级别。…

SpringBoot 配置文件和日志文件

目录 一、SpringBoot配置文件 配置文件的格式 .properties配置文件格式 .yml配置文件格式 .properties 与 .yml的区别 配置文件的读取 .properties 与 .yml的区别 设置不同环境的配置⽂件 二、SpringBoot日志文件 日志打印的步骤 得到日志对象 方法一&#xff1a;使…

【网络】- 计算机网络体系结构 - OSI七层模型、TCP/IP四层(五层)协议

目录 一、概述 二、计算机网络体系结构的形成  &#x1f449;2.1 分层的网络体系结构  &#x1f449;2.2 OSI 参考模型  &#x1f449;2.3 TCP/IP - 事实的国际标准 三、OSI 参考模型 四、TCP/IP 协议 一、概述 但凡学习计算机网络知识&#xff0c;肯定绕不过网络协议的&…

SpringMVC拦截器

SpringMVC拦截器 介绍 拦截器&#xff08;interceptor&#xff09;的作用 SpringMVC的拦截器类似于Servlet开发中的过滤器Filter&#xff0c;用于对处理器 进行预处理和后处理 将拦截器按一定的顺序连接成一条链&#xff0c;这条链称为拦截器链&#xff08;Interception Ch…

hive中如何计算字符串中表达式

比如 select 1(2-3)(-4.1-3.1)-(4-3)-(-3.34.3)-1 col ,1(2-3)(-4.1-3.1)-(4-3)-(-3.34.3)-1 result \ 现在的需求式 给你一个字符串如上述col 你要算出result。 前提式 只有和-的运算&#xff0c;而且只有嵌套一次 -(4-3)没有 -(-4(3-(31)))嵌套多次。 第一步我们需要将运…