Linux线程安全:线程互斥

一、线程互斥的概念

1.1临界资源与互斥的关系

临界资源:多线程执行流共享的资源就叫做临界资源。
临界区:每个线程内部,访问临界资源的代码,就叫做临界区。
互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用。
原子性(后面讨论如何实现):不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成。

1.2互斥量mutex

大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程无法获得这种变量。
但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互。
多个线程并发的操作共享变量,会带来一些问题。
以下面代码为例,创建四个线程同时进行抢票,如果多个线程同时对一个临界资源进行访问而该资源却没有被保护,就会出现bug。
// 操作共享变量会有问题的售票系统代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
int ticket = 100;
void* route(void* arg)
{
	char* id = (char*)arg;
	while (1) {
		if (ticket > 0) {
			usleep(1000);
			printf("%s sells ticket:%d\n", id, ticket);
			ticket--;
		}
		else {
			break;
		}
	}
}

如上图所示,票到最后被枪成了负数而其原因就是代码中没有对临界区资源进行保护,加上临界区对临界资源的操作也并不是原子的,所以最终导致票数成了负数。

1.3汇编角度分析

取出ticket--部分的汇编代码
objdump -d a.out > test.objdump
152 40064b: 8b 05 e3 04 20 00 mov 0x2004e3(%rip),%eax # 600b34 <ticket>
153 400651: 83 e8 01 sub $0x1,%eax
154 400654: 89 05 da 04 20 00 mov %eax,0x2004da(%rip) # 600b34 <ticket>
-- 操作并不是原子操作,而是对应三条汇编指令:
load :将共享变量ticket从内存加载到寄存器中
update : 更新寄存器里面的值,执行-1操作
store :将新值,从寄存器写回共享变量ticket的内存地址。
而线程是随时都有可能被切换的,依照如上代码的逻辑,临界区的操作并不是原子的,CPU先将ticket从内存中取出来放到寄存器中,然后对其进行减法操作,最后再将其放回到内存当中,其中任何一步都有可能导致线程带着上下文数据被切出而内存中ticket的值依然没有改变,导致其他线程进行访问操作时依然拿到的是未被改变的ticket值,然后继续往下执行,而另一个线程此时又将更改过的ticket放回到内存中,此时就会导致tickt的值比规定的下限值要低。
要解决以上问题,需要做到三点:
1.代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区。
2.如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临界区。
3.如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区。
要做到这三点,本质上就是需要一把锁。 Linux 上提供的这把锁叫互斥量。

二、互斥量(锁)的接口

2.1初始化互斥量

初始化互斥量有两种方法:
加锁本质上就是将并行执行变为串行执行。
方法1,静态分配:
如果要定义一把静态的锁,或者是全局的锁,直接定义一个pthread_mutex_t类型的变量,然后通过PTHREAD_MUTEX_INITIALIZER进行初始化就可以使用锁。
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
方法2,动态分配:
如果要定义一把局部的锁,就需要进行手动的初始化以及销毁。
int pthread_mutex_init(pthread_mutex_t* restrict mutex,
 const pthread_mutexattr_t* restrict attr);
参数:
mutex:要初始化的互斥量
attr:NULL

2.2申请及使用锁

加锁

int pthread_mutex_lock(pthread_mutex_t *mutex);

申请锁会出现以下几种状态:

1、申请成功:函数就会返回,允许继续运行。

2、申请失败:函数就会阻塞,不允许继续往下执行。

3、函数调用失败,出错返回。

解锁

int pthread_mutex_unlock(pthread_mutex_t *mutex);

尝试申请锁

int pthread_mutex_trylock(pthread_mutex_t *mutex);

三、互斥量实现原理

经过上面的例子,大家已经意识到单纯的 i++ 或者 ++i 都不是原子的,有可能会有数据一致性问题
为了实现互斥锁操作,大多数体系结构都提供了swap或exchange指令,该指令的作用是把寄存器和内存单元的数据相交换,由于只有一条指令,保证了原子性,即使是多处理器平台,访问内存的 总线周期也有先后,一个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期。 现在我们把lock和unlock的伪代码改一下。
int ticket = 100;
pthread_mutex_t mutex;
void* route(void* arg)
{
	char* id = (char*)arg;
	while (1) {
		pthread_mutex_lock(&mutex);
		if (ticket > 0) {
			usleep(1000);
			printf("%s sells ticket:%d\n", id, ticket);
			ticket--;
			pthread_mutex_unlock(&mutex);
			// sched_yield(); 放弃CPU
		}
		else {
			pthread_mutex_unlock(&mutex);
			break;
		}
	}
}

 通俗来讲:调用锁以后,CPU内会存在一个al寄存器内部存储一个0,在申请锁时,内存中存在mutex内部为1,在一个线程调用锁时,CPU会将al和mutex中的值直接交换,如果此时al为1则说明申请成功,而该线程被中断后会将CPU寄存器中自己的上下文数据全部带走,此时al和mutex中都是0,当下一个线程申请锁时,al交换到的mutex中的内容还是0,此时就只能挂起等待别的线程将锁归还。

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

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

相关文章

274 基于matlab的随机粗糙表面对微气体轴承内气体压强分布的影响

基于matlab的随机粗糙表面对微气体轴承内气体压强分布的影响。采用差分法求解气体轴承的雷诺方程&#xff0c;通过尺寸参数、分形维数对粗糙度表面设置&#xff0c;滑流参数设置&#xff0c;实现气压分布可视化结果显示。程序已调通&#xff0c;可直接运行。 274 气体轴承 随机…

软件设计,建模及需求分析

文章目录 设计原则建模及需求分析重构 设计原则 SOLID原则 单一职责 开闭 &#xff08;扩展开放&#xff0c;修改关闭&#xff09; 里氏替换 &#xff08;父类出现地方都可以用子类替换&#xff09; 接口隔离 依赖倒置&#xff08;高层模块不依赖低层&#xff0c;两层都依…

[数据集][图像分类]茶叶叶子病害分类数据集304张4类别

数据集类型&#xff1a;图像分类用&#xff0c;不可用于目标检测无标注文件 数据集格式&#xff1a;仅仅包含jpg图片&#xff0c;每个类别文件夹下面存放着对应图片 图片数量(jpg文件个数)&#xff1a;304 分类类别数&#xff1a;4 类别名称:[“anthracnose”,“bird_eye_spot”…

三维模型轻量化工具:手工模型、BIM、倾斜摄影等皆可用!

老子云是全球领先的数字孪生引擎技术及服务提供商&#xff0c;它专注于让一切3D模型在全网多端轻量化处理与展示&#xff0c;为行业数字化转型升级与数字孪生应用提供成套的3D可视化技术、产品与服务。 老子云是全球领先的数字孪生引擎技术及服务提供商&#xff0c;它专注于让…

端口映射如何检测?

端口映射是一种网络通信技术&#xff0c;它允许将公网IP地址的特定端口指向内部局域网中的特定设备或应用程序。通过端口映射&#xff0c;可以实现远程访问内部设备&#xff0c;解决了网络环境限制的问题。 在进行端口映射之前&#xff0c;需要进行端口映射检测&#xff0c;以确…

JS:setTimeout计时器优化

setTimeout会因为浏览器的事件循环机制导致计时器的误差&#xff0c;JS代码越复杂、越多&#xff0c;误差越大。 通过使用performance.now()可以一定程度上减小这个误差值。 performance.now()返回的是一个浮点数&#xff0c;表示从页面加载到现在的毫秒数&#xff0c;精度可…

动态数组的实现(仿写ArrayList)

动态数组是什么 之前写过一篇数组和静态数组的介绍&#xff1a;数组的定义和特点&#xff0c;静态数组CURD的实现 我们在静态数组的基础上&#xff0c;增加一些比较方便的功能&#xff0c;比如自动扩容&#xff0c;获取数组长度等&#xff0c;这样的数组叫动态数组 动态数组…

浅析Vue3基础知识(vue3笔记之入门篇)

本文是结合实践中和学习技术文章总结出来的笔记(个人使用),如有雷同纯属正常((✿◠‿◠)) 喜欢的话点个赞,谢谢! 时下Vue框架都是使用Vue3版本来开发项目了,为了加深对Vue3基本知识的了解,特写了这个笔记 1. 生命周期 1.1. vue3生命周期 一个组件从开始到结束,正常的生命周…

【免费】2021年数学建模国赛C题问题一--基于熵权法和TOPSIS法详细版附Word加代码

各位大佬好 &#xff0c;这里是阿川的博客&#xff0c;祝您变得更强 个人主页&#xff1a;在线OJ的阿川 大佬的支持和鼓励&#xff0c;将是我成长路上最大的动力 阿川水平有限&#xff0c;如有错误&#xff0c;欢迎大佬指正 Python 初阶 Python–语言基础与由来介绍 Python–…

EXCEL多sheet添加目录跳转

EXCEL多sheet添加目录跳转 背景 excel中有几十个sheet&#xff0c;点下方左右切换sheet太耗时&#xff0c;希望可以有根据sheet名超链接跳转相应sheet&#xff0c;处理完后再跳回原sheet。 方案一 新建目录sheet&#xff0c;在A1写sheet名&#xff0c;右键选择最下方超链接…

I.MX RT1170之MIPI CSI摄像头初始化和显示流程详解

在上一篇文章I.MX RT1170之MIPI DSI初始化和显示流程详解中&#xff0c;我们介绍了RT1170单片机中MIPI DSI显示屏初始化和显示的详细步骤&#xff0c;那这一节就来介绍MIPI的另一个接口应用&#xff1a;摄像头CSI的初始化和配置流程。 对于摄像头来说&#xff0c;一般我们还要…

软件产品必须要进行鉴定测试吗?测试流程和作用简析

软件产品是现代社会中不可或缺的一部分&#xff0c;它们在商业、娱乐、科技等领域的应用广泛且深入。然而&#xff0c;我们是否关注过这些软件产品的鉴定测试呢?鉴定测试是什么?它的测试流程有哪些?又有什么作用呢?在本文中&#xff0c;我们将为您全面解析这些问题。 鉴定…

大数据学习问题记录

问题记录 node1突然无法连接finalshell node1突然无法连接finalshell 今天我打开虚拟机和finalshell的时候&#xff0c;发现我的node1连接不上finalshell,但是node2、node3依旧可以链接&#xff0c;我在网上找了很多方法&#xff0c;但是是关于全部虚拟机连接不上finalshell&a…

USB HOST DWC3 初始化

https://www.cnblogs.com/newjiang/p/15675746.html 如果dr_mode为device&#xff0c;则初始化gadget。 如果dr_mode为host&#xff0c;需要初始化xHCI驱动。在dwc3_host_init函数的最后调用platform_device_add(xhci)添加platform device&#xff08;xhci-hcd&#xff09;&a…

从当当网批量获取图书信息

爬取当当网图书数据并保存到本地&#xff0c;使用request、lxml的etree模块、pandas保存数据为excel到本地。 爬取网页的url为&#xff1a; http://search.dangdang.com/?key{}&actinput&page_index{} 其中key为搜索关键字&#xff0c;page_index为页码。 爬取的数据…

(九)Spring教程——ApplicationContext中Bean的生命周期

1.前言 ApplicationContext中Bean的生命周期和BeanFactory中的生命周期类似&#xff0c;不同的是&#xff0c;如果Bean实现了org.springframework.context.ApplicationContextAware接口&#xff0c;则会增加一个调用该接口方法setApplicationContext()的步骤。 此外&#xff0c…

[数据集][图像分类]蘑菇分类数据集3122张215类别

数据集类型&#xff1a;图像分类用&#xff0c;不可用于目标检测无标注文件 数据集格式&#xff1a;仅仅包含jpg图片&#xff0c;每个类别文件夹下面存放着对应图片 图片数量(jpg文件个数)&#xff1a;3122 分类类别数&#xff1a;215 类别名称:[“almond_mushroom”,“amanita…

C# 中文字符串转GBK字节的示例

一、编写思路 在 C# 中&#xff0c;将中文字符串转换为 GBK 编码的字节数组需要使用 Encoding 类。然而&#xff0c;Encoding 类虽然默认并不直接支持 GBK 编码&#xff0c;但是可以通过以下方式来实现这一转换&#xff1a; 1.使用系统已安装的编码提供者&#xff08;如果系统…

【Spring Cloud Alibaba】开源组件Sentinel

目录 什么是Sentinel发展历史与Hystrix的异同 Sentinel可以做什么&#xff1f;Sentinel的功能Sentinel的开源生态Sentinel的用户安装Sentinel控制台预备环境准备Sentinel 分为两个部分:下载地址 项目集成Sentinel创建项目修改依赖信息添加启动注解添加配置信息在控制器类中新增…

Java 新手入门:基础知识点一览

Java 新手入门&#xff1a;基础知识点一览 想要踏入 Java 的编程世界&#xff1f;别担心&#xff0c;这篇文章将用简单易懂的表格形式&#xff0c;带你快速了解 Java 的基础知识点。 一、Java 是什么&#xff1f; 概念解释Java一种面向对象的编程语言&#xff0c;拥有跨平台、…