学习系统编程No.32【线程互斥实战】

引言:

北京时间:2023/7/19/15:22,昨天更新完博客,和舍友下了一会棋,快乐就是这么简单,哈哈哈!总体来说,摆烂程度得到一定的改善,想要达到以前的水准,需要一定的契机,毕竟人生在世,快乐最重要是吧!更文带给我的快乐已经没有那么多了,虽然欠了非常多的作业,非常多的课需要补,很多的题等着我去刷,怎叹一个懒字了得,本质还是作息控制不住,哎!这周小目标更文4篇,只要能达到这个水准,其它的都好说,想到还有那么多课没有看,现在真的挺头疼!不管那么多,正式进入该篇博客的正题,承接上篇博客有关多线程互斥和同步相关的知识,该篇博客我们继续深入理解一下有关线程的互斥和同步吧!

在这里插入图片描述

深入线程互斥

承接上篇博客有关线程互斥相关知识,此时我们在深入理解一下线程互斥。在上篇博客中,我们重点强调了为什么要进行线程互斥和如何进行线程互斥,也就是如何让一份共享资源变为临界资源,每次访问共享资源时,只能有一个线程获得资源的使用权(加锁),其他线程必须等待,直到该线程释放资源后才能继续执行(解锁)。并且在此基础上,我们还简单介绍了有关线程互斥的相关线程库接口,如:pthread_mutex_init,pthread_mutex_lock,pthread_mutex_unlock,pthread_mutex_destroy ,当然我们也明白,在使用这些线程互斥接口的前提是我们定义了一个全局的锁,pthread_mutex_t mutex; 在使用文档中,当我们定义了一个全局的锁结构时,该锁结构是一定需要进行初始化和销毁,也就是必须使用pthread_mutex_init接口和pthread_mutex_destroy,但是使用文档中也给我们提供了另一种方法,让我们可以不需要使用这两个接口,就能完成锁结构的初始化和销毁,在定义锁结构时直接在其后面添加对应的宏结构,pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;这样就可以直接在定义锁的同时,完成对锁的初始化和最终锁的销毁,更简便的供给我们使用。那么此时有的同学就会有问题了,有了这个宏定义,还需要之前初始化和销毁的接口干嘛呢?答案是,使用宏定义快速对锁进行初始化的前提是该锁是一个全局变量的锁,只有是全局变量的锁才有资格使用pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;方法,因为如果对应的锁是局部变量,那么就会导致当函数调用完之后,对应的局部变量锁随着栈帧的销毁而销毁,最终导致编译器无法根据地址再找到对应的锁结构,造成资源泄露问题,所以当定义一个局部变量的锁时,此时就只能使用pthread_mutex_init接口和pthread_mutex_destroy接口进行对应的初始化和销毁。当然此时还没有讲清楚,为什么局部变量不能用 PTHREAD_MUTEX_INITIALIZER方法,只有全局变量可以,这是因为如果使用了 PTHREAD_MUTEX_INITIALIZER方法,本质是将对应的锁变量初始化为默认值,并且将其存储在静态区中,使其生命周期与该进程的声明周期相同,当进程结束,操作系统就会自动回收该锁变量占用的资源,达到销毁目的,所以这也就是为什么局部变量锁不能使用 PTHREAD_MUTEX_INITIALIZER方法的原因。

互斥锁细节介绍
搞定了上述知识,此时我们对锁就有了进一步的理解,当然想要彻底搞定锁相关的知识,重点是搞定锁的实现原理,明白它为什么可以让共享资源变成临界资源,不过在了解锁实现原理之前,此时我们先来谈谈有关加锁方面的细节知识,如:一个共享资源只允许使用一把锁进行保护,不然就有可能导致死锁问题。并且在进行加锁时,加锁的位置一定要合理,尽量细化,只要将共享资源保护起来就行,不允许大范围加锁,否则会导致代码执行效率非常低。然后还要明白,对于锁来说,加锁和解锁本身就是一个原子结构,也就是因为锁也属于共享资源,如果不对锁进行保护的话,那么同理会导致竞态条件问题,所以锁的设计者在设计锁的时候,已经将锁设计为原子结构,也就是当一个线程在访问一个锁的时候,别的线程不允许访问该锁。最后还要明白,一个临界区不仅仅只是一行代码,也可能是一批代码,所以当某个线程在执行该临界区中的代码时,该线程有可能会因为时间片到了,而被操作系统调度,使得对应临界区中的代码没有执行完,但是这并不会导致其它线程可以访问对应的临界资源,因为加锁之后,无论临界区的代码是否执行完毕,其它线程都无法访问到对应的临界资源,这也正是加锁之后带来的线程串行化表现。

锁的基本实现原理
搞定了上述知识,我们正式进入锁的实现原理讲解,首先明白,锁本质就是一个互斥量,因为其可以保护共享资源,也就是只让一个线程访问对应的资源,所以我们将这种特性(互斥)称之为锁,也叫互斥锁。明白了这点之后,接下来我们要搞定的也就是互斥量如何进行互斥,从而让多个线程访问共享资源时,只有一个线程能够成功访问,如下图所示:

在这里插入图片描述

如上图所示,此时我们明白,对于所有线程来说,它们在同时访问同一份共享资源时,因为我们进行了加锁操作(pthread_mutex_lock),所以它们在访问该共享资源之前就需要执行加锁接口相关的代码,也就是如上图所示的伪代码,首先它们要将自己上下文中的%al变量初始化为0,然后再使用exchange接口将我们事先定义并且初始化好的锁变量(mutex)从内存中交换到寄存器,也就是交换到自己的上下文中,让%al由0变1,让mutex由1变0,完成了这一步骤之后,对应的线程就实现了互斥操作,也就是我们所说的加锁,并且此时mutex就是该互斥操作中的互斥量。交换的目的就是让这个互斥量只被一个线程拿到,当下一个线程也要交换时,由于mutex互斥量已经变为了锁定状态(0),此时它就无法获取到mutex中的1(未锁定状态),从而无法执行pthread_mutex_lock中的后序代码,只能被操作系统挂起等待(if语句判断)。最后明白一点,也就是我们一直说的锁是共享资源,却不会造成竞态条件的原因是因为其设计成了原子性,从上图我们就能看出,一个线程在执行对应加锁代码时,其中获取互斥量的过程,仅仅就只是一个交换语句,所以可以明白,对于线程来说,单独一句代码,要么执行,要么就是不执行,所以对于加锁操作,它天生就是原子性的。

所以同理解锁操作,就是将mutex的值由0变1,这样,下一个线程在执行加锁操作时,就可以获取到对应mutex互斥量中的1,因为此时mutex处于未锁定状态(1),同理获取到之后(交换)mutex就又会处于锁定状态(0),所以这也是为什么有加锁操作,就一定要有解锁操作,否则就会造成死锁,无论是那个线程都无法访问到该共享资源。

线程封装

明白了上述有关互斥锁的相关知识之后,此时我们进行线程的封装,也就是对pthread.h头文件中有关线程控制相关接口的封装,实现一个自己的简易线程库,当然无论是在C++,还是Java中,它们的线程库都是和我们一样,对pthread.h头文件进行的封装,只不过在设计上不同,所以导致不同的语言在线程库的使用上不同,本质原因就是封装的方法不同,如下代码所示,就是我们自己对线程库的一个封装:

在这里插入图片描述

如上图所示,此时我们就使用Thread类,完成了对线程库的一个简易封装,重点就是注意参数类型和函数指针传参方面的问题,并且还要注意有关静态成员函数相关的知识,也就是如果在一个类中,你因为参数的原因,无法使用this指针,那么此时你就可以使用静态成员函数,使用static声明,这样就完成了该成员函数和类之间的解耦,但是,因为你将该成员函数和类解耦,所以也就导致该成员函数没有this指针,最终导致该成员函数无法访问到类中的成员变量,具体如何解耦这里我们不详谈,这里注意,明白会用就行。

互斥锁的封装

明白了上述有关线程库的封装,此时我们再来看看有关互斥锁的封装,当然此时的封装还是同理对系统接口进行封装,而不是对伪代码进行封装,本质就是为了让我们可以更方便的使用加锁和解锁,如下代码所示:

在这里插入图片描述

此时我们就完成了对锁的封装,那么此时有的同学就会问了,为什么要这样对锁进行封装呢?如下代码所示:本质就是为了让加锁和解锁操作变得更加简易

在这里插入图片描述
如上图所示,通过两种不同的加锁和解锁操作,我们发现,如果将加锁和解锁操作封装在一个类的构造和析构函数中,然后通过对该类对象进行传参,这样可以非常方便的完成对共享资源的保护。

总结:有关线程互斥,互斥锁的基本原理和封装,有关线程等相关知识我们就讲到这里啦!更多有关线程互斥与同步的知识,我们下篇博客见。

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

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

相关文章

UTM 4.3 发布:在 macOS 上优雅的使用 QEMU 虚拟化 Windows、Linux 和 macOS

UTM 4.3 发布:在 macOS 上优雅的使用 QEMU 虚拟化 Windows、Linux 和 macOS 在 iOS 中虚拟化 Windows、Linux 和 Unix 请访问原文链接:https://sysin.org/blog/utm-4/,查看最新版。原创作品,转载请保留出处。 作者主页&#xf…

C语言:杨氏矩阵中查找某数(时间复杂度小于O(N))

题目: 有一个数字矩阵(二维数组), 矩阵的每行从左到右是递增的,矩阵从上到下是递增的, 请编写程序在这样的矩阵中查找某个数字是否存在, 要求:时间复杂度小于O(N)。 思路&#xff1…

《零基础入门学习Python》第057讲:论一只爬虫的自我修养5:正则表达式

如果你在课后有勤加练习,那么你对于字符串的查找应该是已经深恶痛绝了,你发现下载一个网页是很容易的,但是要在网页中查找到你需要的内容,那就是困难的,你发现字符串查找并没有你想象的那么简单,并不是说直…

(已解决)RuntimeError: Java gateway process exited before sending its port number

今天用Pycharm远程使用pysaprk解释器时,跑代码出现了这个错误: RuntimeError: Java gateway process exited before sending its port number 找了好多博客都没解决问题,有说重装spark的,有说本地配Java_home的,后面我…

leetcode 47. 全排列 II

2023.7.23 这道题是上一题全排列 的一个升级版。 唯一区别就是需要增加一个树层去重的操作&#xff0c;因为数组nums中允许有重复的元素了&#xff0c;而上一题没有重复元素。 下面看代码&#xff1a; class Solution { public:vector<vector<int>> ans;vector<…

如何评测一个大语言模型?

编者按&#xff1a;大型语言模型&#xff08;Large language models, LLMs&#xff09;因其在学术界和工业界展现出前所未有的性能而备受青睐。随着 LLMs 在研究和实际应用中被广泛使用&#xff0c;对其进行有效评测变得愈发重要。近期已有多篇论文围绕大模型的评测进行研究&am…

RocketMQ教程-(4)-领域模型-消费者分组ConsumerGroup

定义​ 消费者分组是 Apache RocketMQ 系统中承载多个消费行为一致的消费者的负载均衡分组。 和消费者不同&#xff0c;消费者分组并不是运行实体&#xff0c;而是一个逻辑资源。在 Apache RocketMQ 中&#xff0c;通过消费者分组内初始化多个消费者实现消费性能的水平扩展以…

【云原生】Docker网络及Cgroup资源控制

一、Docker网络 1.docker网络实现原理 Docker使用Linux桥接&#xff0c;在宿主机虚拟一个Docker容器网桥(docker0)&#xff0c;Docker启动一个容器时会根据Docker网桥的网段分配给容器一个IP地址&#xff0c;称为Container-IP&#xff0c;同时Docker网桥是每个容器的默认网关。…

微信小程序——页面跳转方法和场景用法总结

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

如何理解spring cloud 和 spring cloud Alibaba

Spring Cloud是一个基于Spring Framework构建的用于开发和构建分布式系统的开源框架。它提供了一系列的工具和组件&#xff0c;用于帮助开发者快速构建、部署和管理微服务架构。Spring Cloud提供了包括服务发现、配置管理、负载均衡、断路器等在内的众多功能。 Spring Cloud A…

【JVM】JVM执行流程 JVM类加载 垃圾回收机制等

目录 &#x1f337;1、JVM是什么&#xff1f; &#x1f337;2、JVM的执行流程&#xff08;能够描述数据区5部分&#xff09; &#x1f337;3、JVM类加载过程 &#x1f337;4、双亲委派机制&#xff1a;描述类加载的过程 问题1&#xff1a;类加载器 问题2&#xff1a;什么…

支付宝原生小程序组件与父级传递数据(微信小程序基本一样)

1. 声明组件 在对应的目录下,右击点击 新建小程序,之后会生成对应的文件 2. 子组件 Component({data: {colorList: [#165FF6, #3D16F6,

一元多项式的表示及相加

实现思路&#xff1a; 通过链表实现&#xff0c;会更为简单直观。用链表中的每个结点表示多项式中的每一项&#xff0c;多项式每一项都是由数据域&#xff08;包含系数和指数&#xff09;和指针域构成的&#xff0c;所以在定义表示结点的结构体时&#xff0c;可如下所示进行定义…

FFMPEG android mac 编译 支持DASH/OPENSSL问题汇总

一 下载源码 FFMPEG https://github.com/FFmpeg/FFmpeg/tree/release/4.3 二 编写脚本 支持https 就必须添加open SSL 的编译 具体可以查看我的另一个关于open SSL 的 然后我们配置的一些路径 涉及 ndk 的一定要查看你用的ndk 版本 是否存在这些路径这是第一步 然后如果支…

python爬虫入门

基础回顾 使用函数, 先导入, 直接点方法名使用 import math m math.log10(100) print(m)python 交互模式 input输入示例 age int(input("请输入年龄")) age 1 print(age)if else 的使用 和java一样, 只是不加括号, else if 阉割成了 elif 与或非 java : &am…

No3: 学习过程中故障成长积累

文章目录 故障积累1、编码错误导致 故障积累1、编码错误导致

5.2 Bootstrap 过渡效果(Transition)插件

文章目录 Bootstrap 过渡效果&#xff08;Transition&#xff09;插件使用案例 Bootstrap 过渡效果&#xff08;Transition&#xff09;插件 过渡效果&#xff08;Transition&#xff09;插件提供了简单的过渡效果。 注意&#xff1a;如果您想要单独引用该插件的功能&#xff0…

【ACM】—蓝桥杯大一暑期集训Day5

&#x1f680;欢迎来到本文&#x1f680; &#x1f349;个人简介&#xff1a;陈童学哦&#xff0c;目前正在学习C/C、Java、算法等方向&#xff0c;一个正在慢慢前行的普通人。 &#x1f3c0;系列专栏&#xff1a;陈童学的日记 &#x1f4a1;其他专栏&#xff1a;CSTL&#xff…

Qt6 Qt Quick UI原型学习QML第五篇

文章目录 效果QML语法父文件 MyQML.qmlQML语法子文件 TLineEditV1.qmlQML语法子文件 TTextEdit.qml 效果 QML语法父文件 MyQML.qml import QtQuick 2.12 import QtQuick.Window 2.12 import QtQuick.Controls 2.12Window {id: windowvisible: truewidth: 600height: 600title:…

Linux系统进程概念详解

这里写目录标题 冯诺依曼体系结构操作系统(Operator System)1.概念2.目的3.管理4.系统调用和库函数概念 进程1.概念2.描述进程-PCB3.查看进程4.通过系统调用获取进程标示符5.通过系统调用创建进程-fork 进程状态1.Linux内核源代码2.进程状态查看 进程优先级1.基本概念2.查看系统…