【Linux】线程安全问题①——如何实现资源访问互斥(附图解与代码实现)

线程安全主要分为两个方面,分别是资源访问互斥与线程同步(线程协同配合)

本篇博客,我们主要来讲解资源访问互斥这一方面

目录

为什么要实现资源访问互斥?

实现资源访问互斥(原子访问)的经典机制——互斥锁

互斥锁相关函数

使用互斥锁实现资源访问互斥的具体实现

代码实现

结果图示


为什么要实现资源访问互斥?

我们来做一个情景假设

假设现在有两个线程,分别是线程A和线程B,有一个全局变量,名为num,num的初始值位0。

现在这两个线程都要对num这个全局变量进行+1操作,并且都得到了时间片,大家觉得结果一定是2吗?

还真不一定,为什么呢?我来通过几条汇编指令来给大家讲解一下

首先我们要知道 " num = num + 1 ; " 这条语句在计算机中的汇编语言是如何实现的

  1. mov eax , num #将num的数值放入寄存器eax中
  2. add eax  , 1 #将寄存器eax中的数值+1
  3. mov num , eax #再将eax中的数值赋给num

假设线程A和线程B同时获得了num的初始值,也就是他们的第一条汇编指令是同时进行的,不论后续两步谁快谁慢,num最后的结果都是1,因为他们寄存器中num的数值都是0。只有两个线程先后进行对num数值的修改,才能够得到正确的结果2

为什么会出现这种情况呢?就是因为他们没实现对资源的原子访问,两个线程互相将对方的结果覆盖了。所以我们要采用方法杜绝这种情况,实现资源访问互斥,让两个线程对这些共享数据实现原子访问。也就是当自己对这些共享数据进行读写的时候,其余线程不可以对这些数据进行读写操作

实现资源访问互斥(原子访问)的经典机制——互斥锁

实现原子访问的经典机制之一,便是互斥锁机制。要理解互斥锁机制,我们可以拿身边最常见的一个东西来进行举例,那就是卫生间

我做一个情景假设,来帮助大家理解互斥锁机制

假设现在陆续有十个人要来上厕所,卫生间只有一个

  1. 第一个人到了,尝试开卫生间的门(其实也就是尝试获取卫生间门锁的使用权),发现门没有被锁上,于是获得了卫生间门锁的使用权,锁上了卫生间的门并使用卫生间
  2. 第二个人到了,尝试开卫生间的门,发现门被锁上了,于是排在第一个等待卫生间开门
  3. 其他的人到了,尝试开卫生间的门,发现门被锁上了,排在第一个人后面等待卫生间开门
  4. 第一个人出来了,对卫生间的门进行解锁,卫生间现在可供一个人使用
  5. 排在队伍的第一个人获得了卫生间门锁的使用权,锁上了卫生间的门并使用卫生间,后面的步骤同上

卫生间就好比数据,卫生间的门锁就好比互斥锁。

每个线程要想使用这些共享数据,就要先尝试获取互斥锁,如果发现卫生间门锁被锁上,也就是该互斥锁正在被使用的话,这些线程就会进入资源等待队列,等待这个锁的使用权,等到该互斥锁可以使用了,排在最前面的线程就会得到该锁,并对需要访问的共享资源进行上锁,从而正确使用这些共享资源

PS:

  1. 相同共享资源的互斥锁只能有一把,如果有多把互斥锁,每个线程都可以拿着互斥锁对共享资源进行上锁的话,互斥锁的存在就没有意义了
  2. 线程间的共享资源包括:全局资源(全局变量就属于全局资源的一种)、文件描述符、进程信息、堆区空间、信号行为、库空间等
  3. 在互斥锁保护区间的代码也被称为临界区代码,临界区代码越简短越好,否则会影响工作效率
  4. 拿到互斥锁的线程在解锁后可能会再次拿到互斥锁,不是一个线程只能拿一次

如果还是不能够理解的话,我们仍旧拿上面的那两个线程举例,并和上面的情景进行比对

假设现在有两个线程,分别是线程A和线程B,要对初始值位0的全局变量num进行加一操作,步骤如下

步骤卫生间情景线程情景
第一个人到了,尝试获取卫生间门锁的使用权,发现门没有被锁上,于是获得了卫生间门锁的使用权,锁上了卫生间的门并使用卫生间线程A到了,尝试获取互斥锁,发现锁没有被使用,于是获得了该锁的使用权,并对全局变量num进行上锁操作
第二个人到了,尝试开卫生间的门,发现门被锁上了,于是排在第一个等待卫生间开门线程B到了,尝试获取互斥锁,但是锁只有一把,于是便进入资源等待队列等待该锁的使用权
第一个人出来了,对卫生间的门进行解锁,卫生间门锁现在可供一个人使用线程A对全局变量num的+1操作完成,对该互斥锁进行解锁操作,锁现在可以被一个线程使用
第二个人获得了卫生间门锁的使用权,锁上了卫生间的门并使用卫生间线程B获得了该互斥锁的使用权,并对全局变量num进行上锁操作
第二个人出来了,对卫生间的门进行解锁,卫生间被使用了两次, 情景完成线程B对全局变量num的+1操作完成,对该互斥锁进行解锁操作,num的结果为2,情景完成

以上大致就是实现互斥锁机制的具体过程了,接下来我们来了解以下相关函数

互斥锁相关函数

这里我们要用到一个结构体,叫做pthread_mutex_t,这是互斥锁的相关结构体

先介绍下一会会用到的几个变量:

  • pthread_mutex_t lock ; //定义一个互斥锁的结构体
  • const pthread_mutexattr_t attr ; //锁属性相关结构体,使用默认属性就直接传NULL
函数功能返回值
pthread_mutex_init(&lock , &attr);实现互斥锁的初始化成功完成之后会返回0,其他任何返回值都表示出现了错误
pthread_mutex_destroy(&lock);释放锁资源所占用的内存成功返回0,失败返回错误编号
pthread_mutex_lock(&lock);上锁成功返回0,失败返回错误编号
pthread_mutex_unlock(&lock);解锁成功返回0,失败返回错误编号

使用互斥锁实现资源访问互斥的具体实现

在了解了相关函数和实现资源互斥访问的情况下,我们来写一段小代码,来实现一个功能:

  • 两个线程各对全局变量num加5000次,每次加1 (难度:⭐⭐)

PS:要注意的是,不是每个线程一次加满5000次才让另一个线程再加5000次,而是两个线程轮流加,最后都能加满5000次

代码实现

//mutex_lock.c#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <signal.h>
#include <pthread.h>
#include <fcntl.h>//将次数定义成宏
#define CONT 5000//将互斥锁与num定义成全局变量,来让两个线程都可以获取该锁和num
pthread_mutex_t lock;
int num = 0;//两个线程的工作是一样的,所以这里只用定义一个工作函数
void* thread_job(void* arg)
{int i = 0;pthread_detach(pthread_self());//将线程设置为分离态,让系统自己回收//对num进行循环+1操作//注意,不要把锁加在循环外面,如果放在外面,就代表着让一个线程一次性加满5000次后再让另一个线程加//如果把锁家在循环外面,两个线程的工作效率还不如单线程工作效率高while(i < CONT){pthread_mutex_lock(&lock);//上锁//这就是临界区代码,在上锁与解锁之间的代码就是临界区代码num++; i++;printf("thread No.0x%x ++num , num = %d\n" , (unsigned int)pthread_self() , num);pthread_mutex_unlock(&lock);//解锁}pthread_exit(NULL);
}int main()
{//初始化互斥锁pthread_mutex_init(&lock , NULL);pthread_t tids[2];//创造线程A和线程Bpthread_create(&tids[0] , NULL , thread_job , NULL);pthread_create(&tids[1] , NULL , thread_job , NULL);//让主线程循环睡眠,来让线程A和线程B获取时间片//由于我们把线程设置成了分离态,系统会自动回收线程,不用我们操心while(1){sleep(1);}//回收锁资源pthread_mutex_destroy(&lock);exit(0);
}

结果图示

我们可以发现,以上代码实现了该功能并完成了我们的要求,没有让一个线程一次性加满五千次,在加到9310的时候就发生了一次线程转换

以上就是本篇博客的全部内容了,大家有什么地方没有看懂的话,可以在评论区留言给我,咱要力所能及的话就帮大家解答解答

今天的学习记录到此结束啦,咱们下篇文章见,ByeBye!

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

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

相关文章

Nginx正向代理配置(http)

前言 在工作中我们经常使用nginx进行反向代理,今天介绍下怎么进行正向代理,支持http请求,暂不支持https 首先先介绍下正向代理和反向代理。 正向代理 在客户端&#xff08;浏览器&#xff09;配置代理服务器&#xff0c;通过代理服务器进行互联网访问。 反向代理 客户端只…

修改RuoYi部署路径 适配nginx子路径访问

1 官方参考 若依官方文档提供了一种解决方式&#xff1a;前端手册 | RuoYi 若依文档给的已经很明白了&#xff0c;但如果子路径 /admin 发生改变&#xff0c;修改起来就感觉比较费事了&#xff0c;毕竟要修改4个文件。 这里咱们把子路径抽取出来&#xff0c;放到环境配置文件中…

ThinkPHP5小语种学习平台

有需要请加文章底部Q哦 可远程调试 ThinkPHP5小语种学习平台 一 介绍 此小语种学习平台基于ThinkPHP5框架开发&#xff0c;数据库mysql&#xff0c;前端bootstrap。平台角色分为学生&#xff0c;教师和管理员三种。学生注册登录后可观看学习视频&#xff0c;收藏视频&#xf…

关于图像分割SDK的一些基础认识

随着科技的不断发展&#xff0c;图像分割SDK已经成为了一个备受关注的话题。而在众多图像分割SDK中&#xff0c;美摄图像分割SDK以其独特的功能和优势脱颖而出。本文将从美摄图像分割SDK的企业价值和互联网娱乐方面&#xff0c;介绍其宣传文章的具体写作规范。 在企业价值方面&…

从一到无穷大 #18 时序数据库运营SLI思考

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。 本作品 (李兆龙 博文, 由 李兆龙 创作)&#xff0c;由 李兆龙 确认&#xff0c;转载请注明版权。 公有云时序数据库SLA 运营商产品每服务周期服务可用率不低于99.9%衡量服务不可用数据指标从采…

Nginx 同一端口 同时支持http与https 协议

文章目录 需求分析 需求 通过 nginx &#xff0c;让同一端口 同时支持http与https 协议 分析 通过使用 Nginx&#xff0c;可以实现同一端口同时支持 HTTP 和 HTTPS 协议。下面是一种可能的配置方式&#xff1a; 配置 HTTP 服务 在 Nginx 配置文件中&#xff0c;添加以下配置…

利用python学习如何处理需要登录的网站

要处理需要登录的网站&#xff0c;你可以按照以下步骤进行学习&#xff1a; 了解网站的登录机制&#xff1a;登录机制通常有用户名密码登录、OAuth授权登录、Cookie登录等。了解目标网站使用的登录机制是学习处理的第一步。 使用Web抓取工具模拟登录&#xff1a;通过使用工具如…

202、RabbitMQ 之 使用 fanout 类型的Exchange 实现 Pub-Sub 消息模型---fanout类型就是广播类型

目录 ★ 使用 fanout 类型的Exchange 实现 Pub-Sub 消息模型代码演示&#xff1a;生产者&#xff1a;producer消费者&#xff1a;Consumer01消费者&#xff1a;Consumer02测试结果 完整代码ConnectionUtilPublisherConsumer01Consumer02pom.xml ★ 使用 fanout 类型的Exchange …

[Swift]同一个工程管理多个Target

1.准备 先创建一个测试工程“ADemo”&#xff0c;右键其Target选择Duplicate&#xff0c;再复制一个Target为“ADemo2”。 再选择TARGETS下方的“”&#xff0c;添加一个APP到项目中&#xff0c;这个命名为“BDemo”。 2、管理多个Target 可以对三个target分别导入不同的框…

iWall:支持自定义的Mac动态壁纸软件

iWall Mac是一款动态壁纸软件&#xff0c;它可以使用任何格式的漂亮视频&#xff08;无须转换&#xff09;、图片、动画、Flash、gif、swf、程序、网页、网站做为您的动态壁纸、动态桌面&#xff0c;并且可以进行交互。 这款软件功能多、使用简单、体积小巧、不占用资源、运行…

Go 1.21 新内置函数:min、max 和 clear

max 函数 func max[T cmp.Ordered](x T, y …T) T 这是一个泛型函数&#xff0c;用于从一组值中寻找并返回 最大值&#xff0c;该函数至少要传递一个参数。在上述函数签名中&#xff0c;T 表示类型参数&#xff0c;它必须满足 cmp.Ordered 接口中定义的数据类型要求&#xff0…

多个方向工程师岗位缺人,最高25K/月!

高级存储工程师 岗位职责&#xff1a; 1.负责存储设备的巡检&#xff0c;及时解决设备故障&#xff1b; 2.负责定期对存储设备的运行基本状态进行健康性检查和保养等预防性维护服务&#xff1b; 3.负责提供SAN、NAS、iSCSI等存储设备的实时监控&#xff1b; 4.负责提供存储设备…

微信小程序引入阿里巴巴iconfont图标并使用

介绍 在小程序里&#xff0c;使用阿里巴巴的图标&#xff0c;如下所示: 使用方式 搜索自己需要的图标&#xff0c;然后将需要用到的图标加入购物车&#xff0c;如下图所示&#xff1a; 去右上角&#xff0c;点击购物车按钮&#xff1b;这里第一次使用&#xff0c;会有三个提…

Redis缓存穿透、缓存击穿、缓存雪崩详解

目录 缓存处理流程 一、缓存穿透 1、概念 2、解决办法 二、缓存击穿 1、概念 2、解决办法 三、缓存雪崩 1、概念 2、解决办法 缓存处理流程 接收到查询数据请求时&#xff0c;优先从缓存中查询&#xff0c;若缓存中有数据&#xff0c;则直接返回&#xff0c;若缓存中查不到则从…

再学Blazor——概述

简介 Blazor 是一种 .NET 前端 Web 框架&#xff0c;同时支持服务器端呈现和客户端交互性。 使用 C# 语言创建丰富的交互式 UI共享前后端应用逻辑可以生成混合桌面和移动应用受益于 .NET 的性能、可靠性和安全性需要有 HTML、CSS、JS 相关基础&#xff08;开发 UI 框架的话&a…

Steam余额红锁的原因,及红锁后申诉办法

安全的余额一般是通过充值卡充值获得&#xff0c;再加上交易手续费再转卖给你。一般便宜不到哪去。 但你别以为余额是安全的&#xff0c;就万事大吉了。照样有被红锁的可能性&#xff0c;比如这三种&#xff1a; 1、Steam市场巡查机制&#xff0c;红锁 平台的巡查机制和原理…

在逍遥模拟器上安装LPSosed模块以及其Manager管理器

环境&#xff1a;win7 64位&#xff0c;python3.8.10&#xff0c;逍遥模拟器9.0.6&#xff0c;安卓版本9 参考我的文章&#xff1a; 在雷电模拟器9上安装magisk并安装LSPosed模块以及其Manager管理器&#xff08;一&#xff09;-CSDN博客 前置工作&#xff1a;先开启模拟器的…

一文讲解图像梯度

简介&#xff1a; ​ 图像梯度计算的是图像变化的幅度。对于图像的边缘部分&#xff0c;其灰度值变化较大&#xff0c;梯度值变化也较大&#xff1b;相反&#xff0c;对于图像中比较平滑的部分&#xff0c;其灰度值变化较小&#xff0c;相应的梯度值变化也较小。一般情…

测试培训机构「某峰」测评调查报告

测试培训机构「某峰」测评调查报告 ⭐文章简介一、机构测评之受访者介绍二、老师傅-机构测评10问&#xff01;第1问 你从哪里了解到这个培训机构&#xff1f;第2问 你为什么选择这家培训机构&#xff0c;它吸引你掏毛爷爷的点有哪些&#xff1f;第3问 回顾一下&#xff0c;从咨…

CA与区块链之数字签名详解

CA与区块链验证本质上都是数字签名&#xff0c;首先&#xff0c;我们看一下什么是数字签名&#xff01; 数字签名 数字签名是公钥密码学中的一种技术&#xff0c;用于验证信息的完整性和发送者的身份。简而言之&#xff0c;数字签名是一种确认信息来源和信息完整性的手段。它通…