【Linux】线程互斥 与同步

news2025/1/11 2:36:41

文章目录

    • 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,申请锁成功

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

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

相关文章

哈夫曼树(Huffman)【数据结构】

目录 ​编辑 一、基本概念 二、哈夫曼树的构造算法 三、哈夫曼编码 假如<60分的同学占5%&#xff0c;60到70分的占15%…… 这里的百分数就是权。 此时&#xff0c;效率最高&#xff08;判断次数最少&#xff09;的树就是哈夫曼树。 一、基本概念 权&#xff08;we…

Zabbix4.0 自动发现TCP端口并监控

java端口很多&#xff0c;每台机器上端口不固定&#xff0c;考虑给机器配置组不同的组挂载模版&#xff0c;相对繁琐。直接使用同一个脚本自动获取机器上java相关的端口&#xff0c;推送到zabbix-server。有服务端口挂了自动推送告警 一、zabbix-agent配置过程 1、用户自定义参…

Apache Doris :Rollup 物化视图

整理了一下目前开启虚拟机需要用到的程序, 包括MySQL,Hadoop,Linux, hive,Doris 3.5 Rollup ROLLUP 在多维分析中是“上卷”的意思&#xff0c;即将数据按某种指定的粒度进行进一步聚合。 1.求每个城市的每个用户的每天的总销售额 select user_id,city,date&#xff0c; sum(…

树的简单介绍

目录 树的概念 ​ 树的相关概念 树的表示 二叉树的概念 特殊的二叉树 二叉树的存储结构 总结 树的概念 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树&#…

Diffie-Hellman密钥交换协议(Diffie-Hellman Key Exchange,简称DHKE)

文章目录 Diffie-Hellman密钥交换协议&#xff08;Diffie-Hellman Key Exchange&#xff0c;简称DHKE&#xff09;一、密码学相关的数学基础1. 素数&#xff08;质数&#xff09;2. 模运算3. 费马小定理4. 对数5. 离散对数6. 椭圆曲线常见椭圆曲线1. NIST系列曲线secp256k1 2. …

Django实现人脸识别登录

Django实现人脸识别登录 Demo示例下载 1、账号密码登录 2、人脸识别登录 3、注册 4、更改密码 5、示例网站 点我跳转 一、流程说明 1、注册页面:前端打开摄像头,拍照,点击确定后上传图像 2、后端获取到图像,先通过face_recognition第三方库识别是否能够获取到人脸特征…

开闭原则正确姿势, 使用AOP优雅的记录日志, 非常的哇塞

&#x1f473;我亲爱的各位大佬们好&#x1f618;&#x1f618;&#x1f618; ♨️本篇文章记录的为 JDK8 新特性 Stream API 进阶 相关内容&#xff0c;适合在学Java的小白,帮助新手快速上手,也适合复习中&#xff0c;面试中的大佬&#x1f649;&#x1f649;&#x1f649;。 …

使用腾讯云服务器快速搭建网站教程

已经有了腾讯云服务器如何搭建网站&#xff1f;腾讯云服务器网以腾讯云服务器&#xff0c;借助宝塔面板搭建Web环境&#xff0c;然后使用WordPress博客程序搭建网站&#xff0c;大致分为三步&#xff0c;首先购买腾讯云服务器&#xff0c;然后在腾讯云服务器上部署宝塔面板&…

Vue + Vite 构建 自己的ChartGPT 项目

前期回顾 两分钟学会 制作自己的浏览器 —— 并将 ChatGPT 接入_彩色之外的博客-CSDN博客自定义浏览器&#xff0c;并集合ChatGPT&#xff0c;源码已公开https://blog.csdn.net/m0_57904695/article/details/130467253?spm1001.2014.3001.5501 目录 效果图 代码步骤&am…

【Linux】软件包管理器/编辑器/yum是应用商店?/vim编辑器什么?

本文思维导图&#xff1a; 文章目录 Linux软件安装关于Linux的软件生态 1.Linux软件包管理器&#xff1a;yum到底是什么关于yum指令&#xff1a;关于yum源 2. rzsz指令1. Linux编辑器——vim编辑器vim编辑器的三种主要模式vim编辑器命令模式常用快捷键&#xff1a;vim操作总结…

spring(事务管理)

事物可以看做是由对数据库若干操作组成的一个单元 事务的作用就是为了保证用户的每一个操作都是可靠的&#xff0c;事务中的每一步操作都 必须成功执行&#xff0c;只要有发生异常就回退到事务开始未进行操作的状态,这些操作 要么都完成&#xff0c;要么都取消&#xff0c;从而…

Linux:/dev/tty、/dev/tty0 和 /dev/console 之间的区别

在Linux操作系统中&#xff0c;/dev/tty、/dev/tty0和/dev/console是三个特殊的设备文件&#xff0c;它们在终端控制和输入/输出过程中扮演着重要的角色。尽管它们看起来很相似&#xff0c;但实际上它们之间存在一些重要的区别。本文将详细介绍这三个设备文件之间的区别以及它们…

浅谈如何fltk项目编译和实现显示中文

目录 一、编译 二、中文显示如何处理&#xff1a; 2.1在发文2天前突然发现&#xff0c;我这个界面显示英文出现问题了&#xff0c;开始我的搜索之旅&#xff0c;一些参考页面有碰到问题也可以看看&#xff1a; 2.2、 那就开始翻翻官方自带的例程吧&#xff0c;看看他如何显…

Join的连接原理

1. 连接简介 1.1 连接的本质 连接就是把各个表中的记录都取出来进行一次匹配&#xff0c;并把匹配后的组合发送给客户端。如果连接查询中的结果集中包含一个表中的每一条记录与另一个表中的每一条记录相互匹配的组合&#xff0c;那么这样的结果集就可以称为笛卡尔积。 1.2 连…

计算机网络基础知识(七)—— 什么是HTTPS协议?你听我“瞎掰”

文章目录 01 | 工作原理02 | SSL/TLS协议2.1 | 握手协议2.2 | 更换密码协议&#xff08;Change Cipher Spec Protocol&#xff09;2.3 | 警告协议&#xff08;Alert Protocol&#xff09;2.4 | 应用数据协议&#xff08;Application Data Protocol&#xff09; 03 | 加密算法3.…

CSRF及SSRF漏洞案例讲解(29)

讲解一下这个图片&#xff0c;用户在浏览器登陆银行界面发送一个请求&#xff0c;通过转账&#xff0c;转载的数据包假如是下面那串字符&#xff0c;黑客呢就自己一个网站或控制一个网站&#xff0c;去写入一个代码&#xff0c;这个代码就是请求这个数据包&#xff0c;刚好这个…

人工智能学习07--pytorch19--目标检测:常见指标(mAP计算+coco评价标准)

怎样才算正确检测到一个目标&#xff1f; 什么是IOU&#xff1a; https://blog.csdn.net/qq_51831335/article/details/125719420 mAP计算方法&#xff1a; 假设针对某一类别的AP情况 TP&#xff1a;预测正确的边界框个数。预测边界框与GT-box的IOU>0.5 FP&#xff1a;假…

原工程运行正常,重新复制一份后再 npm install 后再运行就报错的解决办法

原工程&#xff0c;运行正常 将刚刚的工程复制一份呢&#xff0c;重新 npm install 再 npm run serve 就报错 出现这个问题十之八九都是依赖的问题。有可能是因为这个工程里面之前安装过一些东西&#xff0c;后来莫名其妙的就把 package.json 里面相关的依赖给删掉了。但由于原…

lwIP 开发指南

目录 lwIP 初探TCP/IP 协议栈是什么TCP/IP 协议栈架构TCP/IP 协议栈的封包和拆包 lwIP 简介lwIP 源码下载lwIP 文件说明 MAC 内核简介PHY 芯片介绍YT8512C 简介LAN8720A 简介 以太网接入MCU 方案 lwIP 无操作系统移植lwIP 带操作系统移植ARP 协议ARP 协议的简介ARP 协议的工作流…

uni-app项目运行和项目结构目录讲解

UNI-APP学习系列 uni-app项目运行和项目结构目录讲解 文章目录 UNI-APP学习系列前言总结 前言 UNI-APP学习系列之uni-app项目运行和项目结构目录讲解 运行项目 使用 pnpm 包管理工具 # 查看是否安装pnpmpnpm -v# 无则安装npm install -g pnpm下载依赖 pnpm i运行pnpm dev:h…