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.rhkb.cn/news/324783.html

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

相关文章

【BUUCTF】Crypto_RSA(铜锁/openssl使用系列)

【BUUCTF】Crypto_RSA&#xff08;铜锁/openssl使用系列&#xff09; 1、题目 在一次RSA密钥对生成中&#xff0c;假设p473398607161&#xff0c;q4511491&#xff0c;e17 求解出d作为flga提交 2、解析 RSA加密过程&#xff1a; 1&#xff09;选择素数&#xff1a;选择两个不…

Zabbix监控中文乱码问题解决方法

一、问题描述 1.查看Zabbix仪表盘 在Zabbix的监控仪表盘界面&#xff0c;字体显示为“方框”&#xff0c;无法查看到具体的性能指标名称。 2.问题分析 Zabbix的web端没有中文字库&#xff0c;导致切换到中文页面&#xff0c;中文成了乱码这个问题&#xff0c;我们最需要把中文…

服务器远程桌面局域网连接不上的解决方法

在企业网络环境中&#xff0c;服务器远程桌面局域网连接不上是一个常见且棘手的问题。这种问题可能导致工作效率下降&#xff0c;甚至影响业务运营。因此&#xff0c;我们需要采取专业的方法来解决这一问题。 服务器远程桌面局域网连接不上的解决方法&#xff1a; 1、确保服务器…

Qt服务器端与客户端交互

Qt做客户端与服务器端交互第一步引入network 第一步引入network后继续编程首先界面设计 创建server和socket 引入QTcpServer&#xff0c;QTcpSocket MainWindow.h代码如下 #ifndef MAINWINDOW_H #define MAINWINDOW_H#include <QMainWindow> #include <QTcpServer&…

Cisco WLC 2504控制器重启后所有AP掉线故障-系统日期时间

1 故障描述 现场1台WLC 2504控制器掉电重启后&#xff0c;所有AP均无线上线&#xff0c; 正常时共有18个AP在线&#xff0c;而当前为0 AP在线数量为0 (Cisco Controller) >show ap sumNumber of APs.................................... 0Global AP User Name..........…

LeetCode 106.从中序与后序遍历序列构造二叉树

LeetCode 106.从中序与后序遍历序列构造二叉树 1、题目 题目链接&#xff1a;106. 从中序与后序遍历序列构造二叉树 给定两个整数数组 inorder 和 postorder &#xff0c;其中 inorder 是二叉树的中序遍历&#xff0c; postorder 是同一棵树的后序遍历&#xff0c;请你构造并…

SSM【Spring SpringMVC Mybatis】——Mybatis

目录 1、初识Mybatis 1.1Mybatis简介 1.2 官网地址 2、搭建Mybatis框架 2.1 准备 2.2 搭建Mybatis框架步骤 1. 导入jar包 2. 编写核心配置文件【mybatis-config.xml】 3. 书写相关接口及映射文件 4. 测试【SqlSession】 2.3 添加Log4j日志框架 导入jar包 编写配置文…

hive日常使用时忘记部分补充(不定时)

1、date_formate、unix_timestamp、from_unixtime用法&#xff1a; 2、lag&#xff08;&#xff09;、lead()用法&#xff1a; lag&#xff08;)窗口函数返回分区中当前行之前行&#xff08;可以指定第几行&#xff09;的值。 如果没有行&#xff0c;则返回null。 lead()窗口…

搭建Docker私服镜像仓库Harbor

1、概述 Harbor是由VMware公司开源的企业级的Docker Registry管理项目&#xff0c;它包括权限管理(RBAC)、LDAP、日志审核、管理界面、自我注册、镜像复制和中文支持等功能。 Harbor 的所有组件都在 Dcoker 中部署&#xff0c;所以 Harbor 可使用 Docker Compose 快速部署。 …

C++ | Leetcode C++题解之第80题删除有序数组中的重复项II

题目&#xff1a; 题解&#xff1a; class Solution { public:int removeDuplicates(vector<int>& nums) {int n nums.size();if (n < 2) {return n;}int slow 2, fast 2;while (fast < n) {if (nums[slow - 2] ! nums[fast]) {nums[slow] nums[fast];slo…

C++:类与对象—继承

类与对象—继承 一、继承是什么&#xff1f;二、继承定义三、基类和派生类对象赋值转换四、继承中的作用域五、派生类的默认成员函数六、继承与友元七、继承与静态成员八、复杂的菱形继承及菱形虚拟继承九、继承的总结和反思十、考察重点 一、继承是什么&#xff1f; 继承(inh…

Flutter-Statewidget 创建State过程State<XXXX> createState() => _XXXXState()的解释

文章目录 创建widget 的状态对象示例代码解析 完整的代码示例总结 创建widget 的状态对象 今天有个同学问了我下State createState() > _XXXXState()时什么意思。这个代码在flutter开发中一直看到&#xff0c;很多人都不关心这个&#xff0c;直接当模板使用。今天来介绍下这…

[Cmake Qt]找不到文件ui_xx.h的问题?有关Qt工程的问题,看这篇文章就行了。

前言 最近在开发一个组件&#xff0c;但是这个东西是以dll的形式发布的界面库&#xff0c;所以在开发的时候就需要上层调用。 如果你是很懂CMake的话&#xff0c;ui_xx.h的文件目录在 ${CMAKE_CURRENT_BINARY_DIR} 下 然后除了有关这个ui_xx.h&#xff0c;还有一些别的可以简…

如何选择合适加密软件来保护信息资产|精选加密软件分析

五款加密软件对比分析&#xff0c;是一项复杂而必要的任务&#xff0c;旨在帮助用户选择最适合其需求的加密工具。在数字化时代&#xff0c;信息安全显得尤为重要&#xff0c;因此&#xff0c;对加密软件的评估与比较显得尤为关键。 首先&#xff0c;我们要考虑的是这些加密软件…

【2024亚马逊云科技峰会】Amazon Bedrock + Llama3 生成式AI实践

在 4 月 18 日&#xff0c;Meta在官网上公布了旗下最新大模型Llama 3。目前&#xff0c;Llama 3已经开放了80亿&#xff08;8B&#xff09;和700亿&#xff08;70B&#xff09;两个小参数版本&#xff0c;上下文窗口为8k&#xff0c;据称&#xff0c;通过使用更高质量的训练数据…

考情分析 | 2025年西北工业大学计算机考研考情分析!

西北工业简称西工大&#xff08;英文缩写NPU&#xff09;&#xff0c;大学坐落于古都西安&#xff0c;是我国唯一一所以同时发展航空、航天、航海工程教育和科学研究为特色&#xff0c;以工理为主&#xff0c;管、文、经、法协调发展的研究型、多科性和开放式的科学技术大学。十…

使用Selenium破解滑动验证码的原理及解决思路

1、获取页面元素信息&#xff1a; 使用Selenium打开目标网页&#xff0c;并通过相关方法获取滑块、背景图等元素的位置和属性信息。可以使用Selenium提供的定位方法&#xff08;如xpath、CSS选择器等&#xff09;来找到这些元素。 可以使用find_element_by_xpath或find_elemen…

Wix打包后安装包直接签名安装失败原因

生成的游戏启动器wix安装包直接打包后进行签名安装会失败&#xff0c;看安装日志显示的错误为 Failed to extract all files from container, erf: 1:2:0 网上搜到的解决方案 需要用insignia工具解包&#xff0c;解包后的文件签一次名&#xff0c;再打一次包&#xff0c;再…

AI伦理和安全风险管理终极指南

人工智能&#xff08;AI&#xff09;正在迅速改变各个领域的软件开发和部署。驱动这一转变的两个关键群体为人工智能开发者和人工智能集成商。开发人员处于创建基础人工智能技术的最前沿&#xff0c;包括生成式人工智能&#xff08;GenAI&#xff09;模型、自然语言处理&#x…

Navicat Data Modeler Ess for Mac:强大的数据库建模设计软件

Navicat Data Modeler Ess for Mac是一款专为Mac用户设计的数据库建模与设计工具&#xff0c;凭借其强大的功能和直观的界面&#xff0c;帮助用户轻松构建和管理复杂的数据库模型。 Navicat Data Modeler Ess for Mac v3.3.17中文直装版下载 这款软件支持多种数据库系统&#x…