Linux线程(二)线程互斥

目录

一、为什么需要线程互斥

二、线程互斥的必要性

三、票务问题举例(多个线程并发的操作共享变量引发问题)

四、互斥锁的用法

1.互斥锁的原理

2、互斥锁的使用

1、初始化互斥锁

2、加锁和解锁

3、销毁互斥锁(动态分配时需要)

五、使用互斥锁改进票务问题

六、可重入与线程安全

 1、可重入(Reentrant)

2、线程安全(Thread Safety)


上篇文章我们讲解了线程的概念以及线程的基本操作:
Linux线程(一)初识线程

这篇文章我们来讲解一下线程互斥的内容。

一、为什么需要线程互斥

        当多个线程试图同时修改同一份数据时,可能会导致数据不一致、竞态条件等问题。

当两个或多个线程同时访问和修改同一个共享资源时,如果没有适当的同步控制,可能会导致数据处于不一致的状态。例如,一个线程正在读取某个变量的同时,另一个线程可能正在修改这个变量,最终结果可能既不是原始值也不是任何一个线程期望修改后的值,造成不可预料的行为。(后面会举例说明)

所以就引出了线程互斥 :
在Linux系统中,线程互斥是一种确保多个线程在访问共享资源时不会产生冲突的机制。这是通过使用互斥锁(Mutex)来实现的,它是防止并发执行线程同时进入临界区(即访问共享资源的代码段)的一种同步原语。

二、线程互斥的必要性

线程互斥是确保多线程环境下程序正确性、稳定性和可预测性的关键手段,通过限制对共享资源的同时访问,避免了并发执行可能引发的各种问题:

避免数据竞争(Data Race):当两个或多个线程同时访问和修改同一个共享资源时,如果没有适当的同步控制,可能会导致数据处于不一致的状态。例如,一个线程正在读取某个变量的同时,另一个线程可能正在修改这个变量,最终结果可能既不是原始值也不是任何一个线程期望修改后的值,造成不可预料的行为。

确保数据一致性:互斥机制确保了在任何时候,最多只有一个线程可以修改共享资源。这样可以保证每次对共享数据的修改都是完整且原子的,从而维护了数据的一致性。

预防竞态条件(Race Condition):竞态条件是指程序的输出依赖于非确定性的线程执行顺序。没有互斥锁,即使程序逻辑正确,由于线程调度的不确定性,也可能导致错误的结果。比如经典的“票务问题”,如果不使用互斥锁,多个线程同时减去票数可能会导致卖出超过实际存在的票数。

实现同步点:除了防止并发访问带来的问题,互斥锁还可以作为线程间的同步工具,用于控制线程执行的顺序。例如,一个线程可能需要等待另一个线程完成特定任务后才能继续执行。

保护资源的完整性:某些资源(如文件、数据库连接、硬件设备等)可能不支持同时访问,或者同时访问会导致错误或损坏。互斥锁确保这些资源在被一个线程使用时,其他线程不能访问,从而保护了资源的完整性。

三、票务问题举例(多个线程并发的操作共享变量引发问题)

我们来看以下代码,多个线程访问一个全局变量ticket来模拟抢票,ticket就是共享变量:

#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(1);printf("%s sells ticket:%d\n",id,ticket);ticket--;}else{break;}}
}int main()
{pthread_t t1,t2,t3,t4;pthread_create(&t1,NULL,route,"thread 1");pthread_create(&t2,NULL,route,"thread 2");pthread_create(&t3,NULL,route,"thread 3");pthread_create(&t4,NULL,route,"thread 4");pthread_join(t1,NULL);pthread_join(t2,NULL);pthread_join(t3,NULL);pthread_join(t4,NULL);return 0;
}

运行后发现

 

票数竟然出现了0和-1,显然是不符合预期的。

每个线程在检查ticket变量是否大于1后,直接进行减操作和打印,没有确保在这两个操作之间没有其他线程也进行了同样的检查和操作。这导致了多个线程可能几乎同时判断ticket大于1,并都执行减1操作,造成票数卖超的错误。

多个线程直接读写共享变量ticket而没有加锁保护,这违反了线程安全原则。当一个线程正在读取ticket的值时,另一个线程可能正在修改它,导致读取到的是不一致或中间状态的数据。

-- 操作并不是原子操作,而是对应三条汇编指令:
load :将共享变量ticket从内存加载到寄存器中
可能同时有几个线程判断了ticket>0,并进行了ticket--操作,但是这个时候ticket的值已经被其他线程修改,这个时候就造成了共享变量的数据错误。
update : 更新寄存器里面的值,执行-1操作
store :将新值,从寄存器写回共享变量ticket的内存地址

解决这些问题的关键是在访问共享资源(这里是ticket变量)之前使用互斥锁(Mutex),确保同一时间只有一个线程能执行临界区内的代码,从而避免了数据竞争和竞态条件,确保了线程安全。 

四、互斥锁的用法

1.互斥锁的原理

加锁(Lock):当一个线程想要进入临界区时,它会尝试获取互斥锁。如果锁未被其他线程持有,该线程将成功获取锁并进入临界区。

解锁(Unlock):完成对共享资源的操作后,线程会释放互斥锁,允许其他等待中的线程有机会获取锁并访问资源。

2、互斥锁的使用

在Linux中,使用POSIX线程库(pthread)来处理线程和互斥锁。以下是基本的使用步骤:

1、初始化互斥锁

静态初始化:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
动态初始化:int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
参数:
mutex:要初始化的互斥量
attr:NULL

2、加锁和解锁

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值:成功返回0,失败返回错误号
调用 pthread_ lock 时,可能会遇到以下情况 :
互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功
发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_ lock调用会陷入阻塞(执行流被挂起),等待互斥量解锁。

3、销毁互斥锁(动态分配时需要)

使用 PTHREAD_ MUTEX_ INITIALIZER 初始化的互斥量不需要销毁
不要销毁一个已经加锁的互斥量
已经销毁的互斥量,要确保后面不会有线程再尝试加锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);

五、使用互斥锁改进票务问题

通过在访问和修改ticket变量前后分别调用pthread_mutex_lock()pthread_mutex_unlock(),确保了在任何时刻只有一个线程能进行售票操作,从而解决了线程间的数据竞争问题,保证了票数的准确减少,避免了超卖现象。

代码示例:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
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--;}else{// 释放锁并跳出循环pthread_mutex_unlock(&mutex);break;}pthread_mutex_unlock(&mutex);}
}int main()
{// 初始化互斥锁pthread_mutex_init(&mutex, NULL);pthread_t t1,t2,t3,t4;pthread_create(&t1,NULL,route,"thread 1");pthread_create(&t2,NULL,route,"thread 2");pthread_create(&t3,NULL,route,"thread 3");pthread_create(&t4,NULL,route,"thread 4");pthread_join(t1,NULL);pthread_join(t2,NULL);pthread_join(t3,NULL);pthread_join(t4,NULL);// 最后记得销毁互斥锁pthread_mutex_destroy(&mutex);return 0;
}

运行后发现保证了票数的准确减少,避免了超卖现象。

经过上面的例子,大家已经意识到单纯的 i++ 或者 ++i 都不是原子的,有可能会有数据一致性问题
为了实现互斥锁操作 , 大多数体系结构都提供了 swap exchange 指令 , 该指令的作用是把寄存器和内存单元的数据相交换, 由于只有一条指令 , 保证了原子性 , 即使是多处理器平台 , 访问内存的 总线周期也有先后 , 一个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期。 

六、可重入与线程安全

 1、可重入(Reentrant)

定义:可重入指的是一个函数或一段代码可以在任意时刻被中断,然后再次进入并正确执行,即使在之前调用还未完成的情况下也是如此。对于可重入代码,最重要的是它的内部状态不会因多次调用而受损,且不依赖于外部状态或存储。

特点

  • 不使用静态或全局变量存储状态信息
  • 不使用用malloc或者new开辟出的空间
  • 如果必须使用全局数据,那么这些数据必须是只读的或能以线程安全的方式修改。
  • 函数不依赖于任何外部资源的状态,或能确保外部资源访问的线程安全性。
  • 递归调用是可重入的一个特例。

2、线程安全(Thread Safety)

定义:线程安全指多个线程同时访问(包括读取和写入)同一段代码或数据时,仍然能够保持正确的执行结果,不会引发数据不一致、崩溃或其他未定义行为。这意味着代码需要采取适当的同步措施(如互斥锁、信号量等)来防止数据竞争和竞态条件。

特点

  • 通过同步机制确保共享资源的访问是互斥的,防止数据竞争。
  • 可能通过加锁机制来实现,但这也会引入潜在的死锁和性能开销。
  • 线程安全的代码在多线程环境下不需要外部干预即可安全运行。

关系:

  • 交集可重入代码通常是线程安全的,因为它不依赖于全局状态,减少了并发访问的冲突点。
  • 区别并非所有线程安全的代码都是可重入的。例如,一个使用了锁来保护共享资源的函数,虽然线程安全(因为一次只有一个线程可以修改资源),但如果在锁内调用自己(递归调用),可能会导致死锁,因此不是可重入的。

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

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

相关文章

RFID在汽车制造中的应用如何改变行业

随着工业4.0和中国制造2025的推进&#xff0c;企业对于智能化、自动化的需求日益增长&#xff0c;RFID射频技术在制造业中已经相当普遍了。在如今这瞬息万变的行业与时代中&#xff0c;RFID技术可以帮助企业获得竞争优势&#xff0c;简化日益复杂的生产流程&#xff0c;推动企业…

C语言实战项目---通讯录

项目要实现的内容&#xff1a;能够存放100个人的通讯录程序&#xff0c;能够实现联系人数据的存储&#xff0c;删除&#xff0c;修改&#xff0c;查找&#xff0c;展示联系人的信息。 所需知识&#xff1a;结构体&#xff0c;指针&#xff0c;函数................. 废话不多…

2016-2021年全国范围的2.5m分辨率的建筑屋顶数据

一、论文介绍 摘要&#xff1a;大规模且多年的建筑屋顶面积&#xff08;BRA&#xff09;地图对于解决政策决策和可持续发展至关重要。此外&#xff0c;作为人类活动的细粒度指标&#xff0c;BRA可以为城市规划和能源模型提供帮助&#xff0c;为人类福祉带来好处。然而&#xf…

Qt之常用控件一

Widget常见属性及其作用 属性作用enabled 设置控件是否可使⽤. true 表⽰可⽤, false 表⽰禁⽤ geometry 位置和尺⼨. 包含 x, y, width, height 四个部分. 其中坐标是以⽗元素为参考进⾏设置的. windowTitle 设置 widget 标题 windowIcon 设置 widget 图标 windowOpa…

java日历类概述

Java中的Calendar类位于java.util包下&#xff0c;它是一个抽象类&#xff0c;用于表示和管理日期及时间。Calendar类并不是直接实例化的&#xff0c;而是通过其提供的静态方法来获取实例。通常情况下&#xff0c;当你尝试创建一个Calendar实例时&#xff0c;实际上你得到的是G…

The 2023 ICPC Asia Hefei Regional Contest

目录 B. Queue Sorting 应该还会再补几题 B. Queue Sorting 题解&#xff1a; Dilworth定理: 【偏序关系与偏序集、Hasse图、极大元、极小元、全序关系、最大元、良序集/三小时讲不完离散数学之集合论/考研复试/期末复习考前冲刺/近世代数/抽象代数】https://www.bilibili.c…

C++奇迹之旅:string类对象的容量操作

文章目录 &#x1f4dd; string类的常用接口&#x1f309; string类对象的容量操作&#x1f320;size&#x1f320;length&#x1f320;capacity&#x1f320;clear&#x1f320;empty&#x1f320;reserve&#x1f309;resize &#x1f6a9;总结 &#x1f4dd; string类的常用…

PMP有效期三年后,还有必要续证吗?

通常情况下是必要的&#xff0c;续证条件是在3年内累积60个PDU和支付150美元。如果到期后没有进行续证&#xff0c;但仍希望保持证书&#xff0c;就需要重新参加PMP考试。重新参加考试的费用包括3900元的报名费和数千元的培训费。因此&#xff0c;与重新考试相比&#xff0c;续…

通过 Java 操作 redis -- 基本通用命令

目录 使用 String 类型的 get 和 set 方法 使用通用命令 exists &#xff0c;del 使用通用命令 keys 使用通用命令 expire,ttl 使用通用命令 type 要想通过 Java 操作 redis&#xff0c;首先要连接上 redis 服务器&#xff0c;推荐看通过 Java 操作 redis -- 连接 redis 关…

思维导图在线怎么制作?推荐这些工具

思维导图在线怎么制作&#xff1f;在如今的快节奏时代中&#xff0c;思维导图作为一种高效的信息组织与思考工具&#xff0c;受到了广泛的应用。在线制作思维导图成打破了时间和空间的限制&#xff0c;使得团队协作变得更加便捷&#xff0c;个人创作也更为灵活。以下是四款备受…

建模电梯的状态图和学生选课ER图

第一题 尝试建模电梯的状态图&#xff08;选做&#xff09; 第二题 学校规定&#xff1a; 一个学生可选修多门课&#xff0c;一门课有若于学生选修。 一个教师可讲授多门课&#xff0c;一门课只有一个教师讲授。 一个学生选修一门课&#xff0c;仅有一个成绩。 学生的属性有学号…

JS中的arguments是什么?

arguments是当我们不确定有多少个参数传递时&#xff0c;就可以使用argument来获取。在js中&#xff0c;arguments实际上就是当前函数的一个内置对象&#xff0c;存储了我们传递的所有实参。arguents的展示形式就是一个伪数组&#xff0c;所以我们可以对它进行遍历。 我们先来…

基于Python的飞机大战游戏

学习目标 了解 飞机大战游戏的规则 理解 面向对象思想,会独立设计游戏的类与模块 掌握 pygame模块的使用 1.1 游戏介绍 飞机大战是一款由腾讯公司微信团队推出的软件内置的小游戏,这款游戏画面简洁有趣,规则简单易懂,操作简便易上手,在移动应用兴起之初曾风靡一时。 1.1.…

oracle 9i 行头带有scn的表

oracle 9i 行头带有scn的表 conn scott/tiger drop table t1; drop table t2; create table t1(c varchar2(5)); create table t2(c varchar2(6)) ROWDEPENDENCIES; --t2表每行都有scn,会增加六个字节的开销 alter table t1 pctfree 0; alter table t2 pctfree 0; insert in…

WordPress原创插件:超链接点击访问统计

WordPress原创插件&#xff1a;超链接点击访问统计 https://download.csdn.net/download/huayula/89296775

【Ajax零基础教程】-----第四课 简单实现

一、XMLHttpRequest对象 通过XMLHttpRequest对象来向服务器发送异步请求&#xff0c;从服务器获取数据。然后用JavaScript来操作DOM而更新页面。XMLHttpRequest是ajax的核心机制&#xff0c;它是IE5中首先引入的&#xff0c;是一种支持异步请求的技术。 简单的说&#xff0c;也…

第四百九十八回

文章目录 1. 概念介绍2. 使用方法2.1 固定样式2.2 自定义样式 3. 示例代码4. 内容总结 我们在上一章回中介绍了"GetMaterialApp组件"相关的内容&#xff0c;本章回中将介绍使用get显示SnackBar.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在介…

ppt通过修改幻灯片母版修改页脚

修改幻灯片母版 幻灯片母版就可以了&#xff0c;就可以修改页脚

1290.二进制链表转整数

给你一个单链表的引用结点 head。链表中每个结点的值不是 0 就是 1。已知此链表是一个整数数字的二进制表示形式。 请你返回该链表所表示数字的 十进制值 。 示例 1&#xff1a; 输入&#xff1a;head [1,0,1] 输出&#xff1a;5 解释&#xff1a;二进制数 (101) 转化为十进制…

即插即用篇 | YOLOv8 引入多光谱通道注意力 | 频率领域中的通道注意力网络

本改进已集成到 YOLOv8-Magic 框架。 注意力机制,尤其是通道注意力,在计算机视觉领域取得了巨大成功。许多工作聚焦于如何设计高效的通道注意力机制,同时忽略了一个基本问题,即通道注意力机制使用标量来表示通道,这很困难,因为会造成大量信息的丢失。在这项工作中,我们从…