【Linux探索学习】第三十弹——线程互斥与同步(上):深入理解线程保证安全的机制

article/2025/6/16 11:30:07

Linux学习笔记:

https://blog.csdn.net/2301_80220607/category_12805278.html?spm=1001.2014.3001.5482

前言:

在上篇我们已经学习了关于线程的大部分知识,包括线程概念和线程控制等内容,今天我们来学习一下使用线程要做到的很重要的一步,那就是要保证线程的同步与互斥,从而确保线程安全问题,我们将通过一些生活中的例子和代码示例来理解如何做到线程的互斥与同步

目录

前提知识

线程的互斥

什么是线程的互斥

为什么需要线程的互斥

互斥的实现方法:互斥量

1. 互斥量的概念

2. 互斥量的接口函数

初始化与销毁

加锁与解锁

尝试加锁

3. 互斥量的使用

互斥锁实现互斥的原理

互斥量的使用场景

保护共享资源

避免竞态条件

互斥量的注意事项

死锁问题

性能开销

总结


前提知识

在深入探讨线程的互斥与同步之前,我们需要了解一些基本概念:

  • 并发与并行:并发是指多个任务在同一时间段内交替执行,而并行是指多个任务在同一时刻同时执行。

  • 临界区:临界区是指访问共享资源的代码段,这些资源在同一时刻只能被一个线程访问。

  • 竞态条件:当多个线程同时访问共享资源时,如果对资源的访问顺序不确定,可能会导致程序的行为不可预测,这种情况称为竞态条件。

线程的互斥

什么是线程的互斥

互斥是指在同一时刻,只允许一个线程访问共享资源。互斥机制确保了一个线程在访问共享资源时,其他线程不能同时访问该资源,从而避免了竞态条件的发生。

为什么需要线程的互斥

在多线程编程中,多个线程可能会同时访问共享资源。如果没有互斥机制,可能会导致以下问题:

  1. 数据不一致:当多个线程同时修改共享数据时,可能会导致数据的不一致。例如,两个线程同时对一个变量进行自增操作,可能会导致最终结果不正确。

  2. 竞态条件:竞态条件是指程序的输出依赖于线程的执行顺序,这会导致程序的行为不可预测。

下面我们通过一个春节购票系统来看一下会有什么问题:

我们定义一个全局变量count=1000表示有1000张票,我们再创建四个新线程,这四个新线程执行抢票工作,主线程等待回收它们,抢票的逻辑具体分为以下三步:

1、先检查是否还有余票。if(count>0)

2、如果有余票就让count--,同时子线程返回抢票的那一步

3、如果没有余票就退出

#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;int count=1000;void *GetTickets(void *args)
{int thread_id = (int)(intptr_t)args; // 获取线程IDwhile(1){if(count>0){usleep(10000);   //模拟购票准备工作所需时间printf("[pthread %d] get a picket, the picket number: %d\n",thread_id,count);--count;}else{//如果没票,直接退出break;}}return nullptr;
}
int main()
{//创建四个新线程pthread_t tids[4];for(int i=0;i<4;i++){pthread_create(&tids[i],nullptr,GetTickets,(void*)(intptr_t)(i+1));}//阻塞等待回收线程for(int i=0;i<4;i++){pthread_join(tids[i],nullptr);}return 0;
}

执行结果(只截取了最后几行):

我们发现出现了错误,按照逻辑票应该是一张一张减少的,而且最终票数到0之后就不能再购买了,但是这里多购买了三张,那么原因是什么呢?

该错误是由以下两个原因组成的:

  • if 语句判断条件为真以后,代码可以并发的切换到其他线程。
  • usleep 是个模拟漫长业务的过程。在这个漫长的业务过程中,可能有其他线程会进入该代码段。

简单点来说就是在一个线程执行购票过程的时候,其它线程也可能会进入进行购票,因为线程是并发执行的,最终就可能会导致票数已经为0了,但是有些进程已经在票数为0前进入到if判断中了,就可能导致以上问题

根本原因就是--操作并不是原子的(简单来说并不是一次完成的),而是对应三条汇编指令:

  • load :将共享变量ticket从内存加载到寄存器中
  • update : 更新寄存器里面的值,执行-1操作
  • store :将新值,从寄存器写回共享变量ticket的内存地址
要解决以上问题,需要做到三点:
  • 代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区。
  • 如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临 界区。
  • 如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区。

要做到以上三点,本质上就是需要一把锁,在一个线程进入临界区时将临界区的入口锁住不让其它线程进入,Linux提供的这个锁叫做互斥量

互斥的实现方法:互斥量

1. 互斥量的概念

互斥量是一种用于多线程编程的同步机制,用于确保同一时刻只有一个线程可以访问共享资源。互斥量的核心思想是通过加锁(Lock)和解锁(Unlock)操作来控制对共享资源的访问,从而避免多个线程同时修改共享资源导致的数据不一致或竞态条件。

互斥量通常用于保护临界区,即访问共享资源的代码段。当一个线程进入临界区时,它会先加锁;当线程离开临界区时,它会解锁,允许其他线程进入。

2. 互斥量的接口函数

1、互斥量的接口函数头文件都是 #include<pthread.h>

2、返回值都是:成功返回0,失败返回相应的错误码

初始化与销毁
pthread_mutex_init

用于初始化互斥量。

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
  • 参数

    • mutex:指向互斥量的指针。

    • attr:互斥量属性,通常设置为NULL以使用默认属性。

  • 返回值:成功返回0,失败返回错误码。

  1. PTHREAD_MUTEX_TIMED_NP:这时默认值,也就是普通锁。当一个线程加锁以后,其它申请该锁的线程组成一个资源等待队列,并在解锁后按优先级获得锁。这种解锁策略保证了资源分配的公平性。
  2. PTHREAD_MUTEX_RECURSIVE_NP:嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁后重新竞争。
  3. PTHREAD_MUTEX_ERRORCHECK_NP:检错锁,如果同一个线程请求同一个锁,则返回EDEADLK错误,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样就保证当不允许多次加锁时,不会出现最简单情况下的死锁。
  4. PTHREAD_MUTEX_ADAPTIVE_NP:适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。
pthread_mutex_destroy

用于销毁互斥量,释放相关资源。

int pthread_mutex_destroy(pthread_mutex_t *mutex);
  • 参数

    • mutex:指向互斥量的指针。

  • 返回值:成功返回0,失败返回错误码。

加锁与解锁
pthread_mutex_lock

用于加锁互斥量。如果互斥量已被其他线程锁定,当前线程会被阻塞,直到互斥量被解锁。

int pthread_mutex_lock(pthread_mutex_t *mutex);
  • 参数

    • mutex:指向互斥量的指针。

  • 返回值:成功返回0,失败返回错误码。

pthread_mutex_unlock

用于解锁互斥量,允许其他线程加锁。

int pthread_mutex_unlock(pthread_mutex_t *mutex);
  • 参数

    • mutex:指向互斥量的指针。

  • 返回值:成功返回0,失败返回错误码。

尝试加锁
pthread_mutex_trylock

尝试加锁互斥量。如果互斥量已被其他线程锁定,函数立即返回,不会阻塞当前线程。

int pthread_mutex_trylock(pthread_mutex_t *mutex);

参数

  • mutex:指向互斥量的指针。
  • 返回值:成功返回0,失败返回错误码(如EBUSY表示互斥量已被锁定)。

在锁的初始化上除了上面用函数来初始化外,我们也可以直接初始化:

这种初始化的方法叫做静态初始化,它将锁的声明、定义和初始化全部完成了,而且这样初始化的锁最后也不需要我们手动销毁

3. 互斥量的使用

我们使用一把互斥锁来对我们上面的购票系统进行一下加工:

首先我们先定义一个全局的互斥量,因为多个线程都要使用,定义全局的可以提高效率

主线程对所进行初始化,然后等待子线程都执行完后,回收子线程并且销毁锁

子线程在进入临界区时加锁,出临界区时解锁

#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;int count=1000;
pthread_mutex_t mutex;void *GetTickets(void *args)
{int thread_id = (int)(intptr_t)args; // 获取线程IDwhile(1){//进入临界区时加锁pthread_mutex_lock(&mutex);if(count>0){usleep(10000);   //模拟购票准备工作所需时间printf("[pthread %d] get a picket, the picket number: %d\n",thread_id,count);--count;pthread_mutex_unlock(&mutex);    //买完票后退出重新进入购票队列,并把锁释放}else{//如果没票,直接退出pthread_mutex_unlock(&mutex);break;}}return nullptr;
}
int main()
{//创建新线程前先把锁初始化了pthread_mutex_init(&mutex,nullptr);//创建四个新线程pthread_t tids[4];for(int i=0;i<4;i++){pthread_create(&tids[i],nullptr,GetTickets,(void*)(intptr_t)(i+1));}//阻塞等待回收线程for(int i=0;i<4;i++){pthread_join(tids[i],nullptr);}//线程都回收后把锁也销毁了pthread_mutex_destroy(&mutex);return 0;
}

运行结果:

此时我们就发现我们最终的执行结果没有出现上面的问题了,这就是因为互斥锁保证了ticket--的原子性问题,每一次票数减一后才会有其它线程进入到临界区中

但是我们观察上面的执行结果,我们发现怎么所有的票都被线程2抢走了呢?这是因为最一开始是线程2先拿到锁抢到票,但是之后线程2对锁的竞争力就会远强于其它线程了,因为它将锁刚一释放就马上又获取了,所以我们采取的方法可以是:线程2抢完票之后可以让它短暂睡眠一会儿,这样其它线程就能够来争夺锁了

再次运行:

此时我们就可以发现所有线程都参与到抢票中来了,符合我们的预期结果

互斥锁实现互斥的原理

在了解锁的原理前,首先我们先来看一个小的知识点:

我们都知道寄存器是32位的,但是早期的寄存器实际上是只有16位的,现在的寄存器实际上可以看成两个16位的寄存器组合在一起,看成al和ah两块,al就是低位的那块,ah就是高位的那块

锁的原理图:

  • 第一步:首先将al寄存器清零
  • 第二步:将al寄存器中的内容和mutex的内容做一次交换,这个动作其实就是我们申请锁的动作
  • 第三步:判断,如果锁的数量大于0就会申请成功并退出,如果不大于0就会挂起等待

下面来看一下锁的伪代码对应的各个过程:

在整个加锁的过程中只有一个数值1存在,这个1要么保存在某个线程的私有的寄存器中,要么保存在共享的互斥锁变量mutex里。如果某个线程的寄存器中的值为1,说明该线程已经加锁成功。

锁本身其实也是作为共享资源存在的,那锁本身需要保护吗?

答案是不需要,因为锁在执行各种操作时其实已经是互斥的了

互斥量的使用场景

保护共享资源

互斥量最常见的用途是保护共享资源,例如全局变量、文件、数据库连接等。通过加锁和解锁操作,可以确保同一时刻只有一个线程访问共享资源。

避免竞态条件

互斥量可以避免竞态条件的发生。例如,在多个线程同时修改同一个变量时,使用互斥量可以确保每次修改操作是原子的。


互斥量的注意事项

死锁问题

死锁是指多个线程相互等待对方释放锁,导致程序无法继续执行。常见的死锁场景包括:

  • 线程A锁定互斥量X,然后尝试锁定互斥量Y;

  • 线程B锁定互斥量Y,然后尝试锁定互斥量X。

为了避免死锁,可以遵循以下原则:

  • 按固定顺序加锁;

  • 使用超时互斥量;

  • 避免嵌套加锁。

性能开销

互斥量的加锁和解锁操作会带来一定的性能开销,尤其是在高并发场景下。为了减少开销,可以:

  • 尽量减少临界区的范围;

  • 使用读写锁(std::shared_mutex)替代互斥量;

  • 使用无锁数据结构(Lock-Free Data Structures)。

总结

互斥量是多线程编程中不可或缺的同步机制,用于保护共享资源、避免竞态条件和数据不一致问题。通过加锁和解锁操作,互斥量确保同一时刻只有一个线程访问临界区。

在实际开发中,需要根据具体场景选择合适的互斥量类型,并注意避免死锁和性能开销问题。通过合理使用互斥量,可以编写出高效、可靠的多线程程序。

 除了互斥外,同步也是保证线程安全的很重要的概念,鉴于篇幅问题,同步我们放在下一篇进行讲解

本篇笔记:

感谢各位大佬观看,创作不易,还请各位大佬点赞支持!!!


http://www.hkcw.cn/article/wXyfARHlUN.shtml

相关文章

飞牛的ipv6已动态解析到阿里云,访问显示不安全?教你绑定免费SSL证书

前言 最近有很多小伙伴陆续通过DDNS-GO做好了飞牛的ipv6动态解析了&#xff0c;如果还没有弄&#xff0c;又不知道文章在哪&#xff0c;可以点击下方这篇教程&#xff1a; 飞牛NAS有IPV6&#xff0c;想用DDNS-GO动态解析到域名&#xff1f;这简单了&#xff01; 很多搞定了的…

银河麒麟桌面操作系统V10 SP1:取消安装应用的安全授权认证

银河麒麟桌面操作系统V10 SP1&#xff1a;取消安装应用的安全授权认证 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 使用银河麒麟V10 SP1安装应用时&#xff0c;若频繁遇到安全授权认证提示&#xff0c;可按以下步骤设置&#xff1a; 打开…

最新Spring Security实战教程(十二)CORS安全配置 - 跨域请求的安全边界设定

&#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Micro麦可乐的博客 &#x1f425;《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程&#xff0c;入门到实战 &#x1f33a;《RabbitMQ》…

解决Windows安全中心显示空白页面

1、电脑重装系统后&#xff0c;发现原本一些软件打不开了&#xff0c;电脑莫名认为有病毒&#xff0c;自动删除插件。附图。 2、第一反应是电脑防火墙的原因&#xff0c;默认威胁防护识别到了病毒软件&#xff0c;自动删除。在开始屏幕搜Windows安全中心&#xff0c;打开之后发…

Python全流程开发实战:基于IMAP协议安全下载个人Gmail邮箱内所有PDF附件

文章目录 一、需求分析与安全前置&#xff1a;为什么需要专用工具&#xff1f;1.1 痛点场景1.2 技术方案选择 二、准备工作&#xff1a;Gmail账号安全配置与环境搭建2.1 开启两步验证&#xff08;必做&#xff01;&#xff09;2.2 创建应用专用密码&#xff08;替代普通密码&am…

【网络安全论文】筑牢局域网安全防线:策略、技术与实战分析

【网络安全论文】筑牢局域网安全防线:策略、技术与实战分析 简述一、引言1.1 研究背景1.2 研究目的与意义1.3 国内外研究现状1.4 研究方法与创新点二、局域网网络安全基础理论2.1 局域网概述2.1.1 局域网的定义与特点2.1.2 局域网的常见拓扑结构2.2 网络安全基本概念2.2.1 网络…

评论员点评郑钦文无缘四强 体能优势未转化

北京时间6月3日,在2025年法网女单1/4决赛中,郑钦文以0-2的成绩不敌萨巴伦卡,遗憾未能晋级四强。赛后,资深评论员许旸对比赛进行了点评。许旸表示,这场比赛让人感到不甘心,郑钦文未能将体能优势转化为战斗力。特别是在第二盘,不仅体能没有得到充分利用,技术上的发挥也非…

【吾爱】逆向实战crackme160破解记录(一)

前言 最近想拿吾爱上的crackme程序练练手&#xff0c;发现论坛上已经有pk8900总结好的160个crackme&#xff0c;非常方便&#xff0c;而且有很多厉害的前辈已经写好经验贴和方法了&#xff0c;我这里只是做一下自己练习的记录&#xff0c;欢迎讨论学习&#xff0c;感谢吾爱论坛…

媒体:硬碰硬才是郑钦文的底色 移山之路无悔前行

面对高山,有人绕路,有人架桥,郑钦文选择移山。第八次和萨巴伦卡交手,郑钦文用硬碰硬的方式展示自己强悍的底色。虽然再次失败,依然无怨无悔。这才是郑钦文的网球为人所爱的原因。她的正手轰球已经让高山颤抖。如果郑钦文在第一盘4比2领先时拿下那两次网前机会球,比赛结果…

德国驻华大使馆祝樊振东大展身手 加盟萨尔布吕肯开启新挑战

6月3日,德国驻华大使馆发文祝贺樊振东加盟德国萨尔布吕肯乒乓球俱乐部。奥运冠军、世界冠军樊振东正式成为德国乒乓球甲级联赛(TTBL)俱乐部——萨尔布吕肯1.FC的一员。樊振东表示,他非常期待在萨尔布吕肯和TTBL的全新挑战,渴望成为这家俱乐部的一员,体验新的环境,并与球…

“俄版珍珠港事件”会否引爆核战争 乌无人机袭击引发紧张态势

就在俄乌定于6月2日举行第二轮直接谈判前夕,当地时间6月1日,俄罗斯境内发生了一系列事件。先是桥梁因爆炸坍塌,随后五个空军基地遭遇大规模无人机袭击。乌克兰安全局宣称对此次袭击负责,这是自俄乌冲突爆发以来乌军对俄领土发动的最具渗透性的袭击之一。俄罗斯国防部认定这…

5个月女婴心脏手术后出现脑损伤 家属质疑治疗过程

近日,四川的徐女士反映,她5个多月大的孩子鱼鱼在四川大学华西第二医院锦江院区做完心脏相关手术后,头部莫名出现一个创口。经检查,鱼鱼被诊断为脑出血和脑损伤,后续还伴有癫痫。当地卫健委介入调查后未能得出明确结论。5月29日,记者在事发医院看到,已经一岁多的鱼鱼仍旧…

SpringBoot1--简单体验

1 Helloworld 打开&#xff1a;https://start.spring.io/ 选择maven配置。增加SpringWeb的依赖。 Generate之后解压&#xff0c;代码大致如下&#xff1a; hpDESKTOP-430500P:~/springboot2/demo$ tree ├── HELP.md ├── mvnw ├── mvnw.cmd ├── pom.xml └── s…

乌蛛网行动有多惊艳 无人机开启新战局

乌克兰在大规模使用无人机发动特种攻击方面再次震惊了世界。6月1日,乌克兰国家安全局对俄罗斯境内多地的军用机场发动大规模无人机攻击,声称摧毁了“多达41架战略轰炸机”。俄罗斯方面表示乌军造成的损失没有那么大,但承认了乌军无人机成功袭击的事实。西方媒体称,这种无人…

永辉超市设首席采购官一职 供应链改革加码

6月3日,永辉超市确认佘咸平已就任该公司首席采购官,负责采购和供应链方面的事务。这一任命是永辉超市在供应链改革方面的重要举措。公开资料显示,佘咸平于2002年加入山姆中国采购部,先后担任过山姆北方区采购总监、全国生鲜采购总监等职位。2016年,他加入盒马,担任过盒马…

男子被困河道中间 飞手用无人机救援 2分钟成功营救

男子被困河道中间 飞手用无人机救援 2分钟成功营救!近日,在浙江丽水,一名垂钓爱好者因突降暴雨导致河水暴涨而被困河道中央。当地无人机飞手陈先生迅速出动,仅用2分钟就成功将被困者吊运至安全地带。执行此次救援任务的无人机原本用于农业植保作业,其最大起飞重量符合国家…

外国游客在三亚连救两溺水者 英勇行为获网友点赞

6月2日,一名外国游客在海南三亚勇救两名溺水者的视频引起了广泛关注。许多网友留言表示希望能找到这位救人者,请他吃饭。据视频拍摄者称,2日晚7时20分左右,在三亚椰梦长廊附近海域有人溺水并呼救。他立即拨打了报警电话。这时,一名外国游客毫不犹豫地朝溺水者游去,并成功…

让女童漫展上擦边直播 谁出的馊主意 未成年人保护不容忽视

在广东中山的一场动漫展上,几名衣着暴露的儿童正在进行直播,引起了现场观众的不满。据网传图片显示,两名女童身穿紧身服,脚穿半截丝袜,在一处临时搭建的简易摄影场地前进行表演。主办方工作人员表示,他们已联系家长和摄影工作室,及时制止了这一行为。该事件不仅让现场观…

记者发现成都仍有火车票代售点 引发共鸣与回忆

如今,人们习惯用手机购买火车票,通过12306平台购票已成为常态,甚至不再需要取纸质票或打印电子发票。然而,近日有网友在社交平台上分享了自己在线下火车票代售点成功购票的经历,让人感到意外。这名网友回忆起上次去火车票代售处排队购票的情景,并讲述了自己在成都的一次购…

车企价格战不断 经销商商会“喊话” 抵制内卷竞争

车市价格战再次引发关注,全国工商联汽车经销商商会呼吁停止以“价格战”为主要形式的“内卷式”竞争。近两年,“价格战”成为国内车市的热点话题。数据显示,今年4月,国内乘用车市场均价为17万元,同比下降2.1万元。不仅车价下降,汽车行业利润也受到影响。据统计,今年前四…