Linux线程互斥与同步

news2024/11/26 2:37:08

目录

🍑一、线程互斥

1.1、进程线程间互斥相关背景概念

1.2、互斥量mutex

1.3、互斥量的接口

1.4、互斥量使用

1.5、互斥量实现原理探究

1.6、RAII风格的设计加锁

1.7、可重入VS线程安全

🍑二、常见锁概念

2.1、死锁

🍑三、Linux线程同步

🍑一、线程互斥

一个技术的产生必定是为了解决出现的某些问题,那么是哪些问题促使产生了线程互斥这一项技术呢?

我们模拟多线程抢火车票。

 

 发现有0,-1,-2的票号,这很显然是错误的。为什么会这样呢?

这是因为有这样的实际场景,当tickets==1时假设这时4个线程同时进来,同时判断。假设为单CPU模式,这时四个线程都认为tickets为1。因为要usleep,所以四个线程都去休眠CPU轮转切换线程。当thread1进来时,因为它的上下文认为tickets是1,读取tickets,tickets--,再写回。当thread2进来时,因为它的上下文也认为tickets是1,读取tickets(这时实际已经是0),tickets--,再写回tickets,这时tickets已经是-1了......

有这样的场景出现就会导致上述现象。

对一个全局变量进行多线程更改不是安全的

上面是我们的推测,那么从底层汇编分析:

--tickets操作并不是原子操作,而是对应三条汇编指令:
🖊load:将共享变量ticket从内存加载到寄存器中
🖊update:更新寄存器里面的值,执行-1操作
🖊store:将新值,从寄存器写回共享变量ticket的内存地址
三条汇编指令如何导致线程不安全呢?举个例子:
当线程A对tickets进行--时,它需要进行三个步骤。当tickets读取到CPU寄存器中,让CPU进行算术和逻辑运算,这时tickets运算结果比如为999,但是当线程A正要将数据写回到内存中变量的位置时,CPU将线程A切走了,而是切换为线程B(这时内存中tickets还是1000)而线程B也是执行三个步骤,取tickets,运算,写回,但是它执行了300次,导致内存中tickets变为了700.这时CPU再切回线程A,线程A根据它的上下文,继续执行第三个步骤,将运算好的tickets写回到内存,tickets又变为了999,这就体现了不安全。
怎么解决这些线程不安全的问题呢?这就要涉及到 线程互斥的概念了。

1.1、进程线程间互斥相关背景概念

🖊临界资源:多线程执行流共享的资源就叫做临界资源。

🖊临界区:每个线程内部,访问临界资源的代码,就叫做临界区。--临界区往往是线程代码的很小的一部分

🖊互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用。

🖊原子性:不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成。(不专业的说,一个对资源进行的操作,如果只用了一条汇编就能完成,就叫做原子性。)

解决方案是加锁

1.2、互斥量mutex

要解决以上问题,需要做到三点:
🖊 代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区。
🖊如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么 只能允许一个线程进入该临界区
🖊 如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区。  

要做到这三点,本质上就是需要一把锁,Linux上提供的这把锁叫互斥量

1.3、互斥量的接口

初始化互斥量

pthread_mutex_init: 对锁进行初始化。

pthread_mutex_destroy:销毁锁

锁的类型是pthread_mutex_t.

初始化互斥量有两种方法:

🖊方法一:静态分配

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER

🖊方法二:动态分配 

int pthread_mutex_init(pthread_mutex_t * restrict mutex, const pthread_mutexattr_t * restrictattr);

参数:

        mutex:要初始化的互斥量

        attr:nullptr

这两种方式的区别在于,静态分配通常在全局,而且一旦分配不需要我们手动销毁互斥量,而是会自动销毁。而动态分配则一般应用在局部,比如函数内,需要手动初始化(pthread_mutex_init)和手动销毁(pthread_mutex_destroy).

销毁互斥量需要注意:

🖊使用PTHREAD_MUTEX_INITIALIZER初始化的互斥量不需要销毁

🖊不要销毁一个已经加锁的互斥量

🖊已经销毁的互斥量,要确保后面不会有线程再尝试加锁

int pthread_mutex_destroy(pthread_mutex_t *mutex);

互斥量加锁和解锁

int pthread_mutex_lock(pthread_mutex_t *mutex);

int pthread_mutex_trylock(pthread_mutex_t *mutex);

int pthread_mutex_unlock(pthread_mutex_t * mutex);

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

调用pthread_lock时,可能遇到以下情况:

🖊互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功

🖊发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_lock调用会陷入阻塞(执行流被挂起),等待互斥量解锁。

🖊加锁和解锁之间的资源就是临界资源,代码就是临界区。

1.4、互斥量使用

使用锁再来抢一下火车票:

 

加锁和解锁的过程多个程序串行执行,一次只允许一个进程进入,所以执行的速度会变慢。锁只规定互斥访问,没有规定必须让谁优先执行,锁就是真正地让多个执行流进行竞争的结果。 

可以将锁封装成类

 

 如果申请锁成功,就继续向后执行,如果申请暂时没有成功,执行流会阻塞:

 我们也可以使用接口pthread_mutex_trylock()如果加锁成功就成功加上锁,如果加锁失败就立马出错返回,这是申请锁的一种非阻塞获取的方式

🖊当一个线程申请锁成功,进入临界资源,当它正在访问临界资源时,其他线程在阻塞等待

🖊申请锁成功的线程可以被切换,但是当一个持有锁的线程被切走,其他线程依旧无法申请锁成功(锁没有释放),无法向后执行,其他线程处于阻塞状态。

🖊站在其他线程的角度,有意义的锁的状态,只有申请锁前和释放锁后这两种,而线程持有锁的过程就是原子的

🖊未来在使用锁的时候,一定要尽量保证临界区的粒度要非常小(可以理解为代码量少)

1.5、互斥量实现原理探究

🖊经过上面的例子,已经意识到单纯的++或者--都不是原子的操作,有可能有数据一致性问题。

🖊为了实现互斥锁操作,大多数体系结构都提供了swap或者exchange指令,该指令的作用是把寄存器和内存单元的数据相交换,由于只有一条指令,保证了原子性,即使是多处理器平台,访问内存的总线周期也有先后,一个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期。汇编指令只有一条保证了原子性

这个图是互斥量实现原理的伪代码,为了更方便解释,它结合下图,我进行一些简单的解释。 

 

 如果使用了互斥量,那么就会把CPU中al赋予0,然后内存中mutex内的值为1,当线程A申请到了锁,那么就会在CPU上允许线程A的上下文时,将内存中的mutex中的1与al中的0交换。寄存器中的值属于当前线程,也就是线程A的上下文。线程A将锁拿走了。因为锁原来是临界资源,是共享的,但是因为exchange汇编指令,把锁从临界资源代入到了寄存器。寄存器中的资源属于当前线程的上下文,所以当切走线程A时,因为线程A要带走自己的上下文数据,导致他把锁拿走了。而切来的线程B,因为访问不到锁,申请锁不成功,根据伪代码:if(al 寄存器内的内容>0)判断不成立,就只能else 挂起等待了。

其他线程申请不了锁,申请失败,只能等切回线程A,因为他要恢复上下文,它持有锁,所以它是满足判断条件的。它会继续执行它的内容。

当线程A解锁时,将寄存器中的锁中的1,再拷贝回去。

 解锁完成,其他线程以同样的逻辑再来竞争锁,占有锁,解锁。

1.6、RAII风格的设计加锁

 

这种设计只需要创建就不用再管了,初始化就完成了加锁,当退出局部域自动销毁对象,因为析构调用了解锁。这种风格称为RAII风格的加锁

1.7、可重入VS线程安全

#概念

🖊线程安全:多个线程并发同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作,并且没有锁保护的情况下,会出现该问题。

 🖊重入:同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,我们称之为重入。一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重入函数,否则是不可重入函数。 

#常见的线程不安全的情况

🖊不保护共享变量的函数

🖊函数状态随着被调用,状态发生变化的函数

🖊返回指向静态变量指针的函数

🖊调用线程不安全函数的函数 

# 常见的线程安全的情况

🖊每个线程对全局变量或者静态变量只有读取的权限,而没有写入的权限,一般来说这些线程是安全的。

🖊类或者接口对于线程来说都是原子操作

🖊多个线程之间的切换不会导致该接口的执行结果存在二义性

# 常见不可重入的情况

🖊调用了malloc/free函数,因为malloc函数是用全局链表来管理堆的。

🖊调用了标准I/O库函数,标准I/O库的很多实现都以不可重入的方式使用全局数据结构。

🖊可重入函数体内使用了静态的数据结构。

常见可重入的情况

🖊不使用全局变量或静态变量

🖊不使用用malloc或者new开辟的空间

🖊不调用不可重入函数

🖊不返回静态或全局数据,所有数据都由函数的调用者来提供

🖊使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据

可重入与线程安全联系

函数是可重入的,那就是线程安全的。

函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题

如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的

可重入与线程安全区别

可重入函数是线程安全函数的一种

线程安全不一定是可重入的,而可重入函数则一定是线程安全的。

如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数若锁还未释放则会产生死锁,因此是不可重入的。

🍑二、常见锁概念

2.1、死锁

死锁是指在一组进程中的各个线程均占有不会释放的资源,但因互相申请被其他线程所占有不会释放的资源而处于的一种永久等待状态

##死锁的四个必要条件

🖊互斥条件:一个资源每次只能被一个执行流使用

🖊请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放

🖊不剥夺条件:一个执行流已获得的资源,在未使用完之前,不能强行剥夺

🖊循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系

#避免死锁

既然这四个必要条件形成了死锁,那么要破坏死锁也是破坏这四个必要条件中的至少一个条件。

🖊加锁顺序一致

🖊避免锁未释放的场景

🖊资源一次性分配

什么意思呢?

🖊破坏请求与保持条件

比如线程持有一把锁,当它申请第二把锁失败的时候,不保持,而是先释放自己持有的锁。此时就不会造成死锁了。

🖊破坏不剥夺条件

线程A持有A锁,线程B持有B锁,那么线程A申请要B锁,而线程B申请要A锁,这时我们要破坏不剥夺,比如根据线程优先级或者线程状态来决定谁可以剥夺谁的锁,破坏死锁条件。

🖊破坏循环等待条件

不能申请锁是环状申请,比如线程A申请A锁,B锁;线程B申请B锁,C锁;线程C申请C锁,D锁。这样天然设计为环状锁会容易导致环路等待条件。申请锁的顺序保持一致,就可以破坏它的环路等待条件。

顺带提一嘴,一个线程申请了锁,另一个线程是可以解它的锁的。

🍑三、Linux线程同步

线程同步是为了解决一类问题:一个线程频繁申请释放锁,而其他线程长时间申请不到锁,其他线程处于饥饿状态

为了必须设定条件,当一个线程刚释放完锁,不能立马申请,而是必须按照一定的顺序排到其他线程的后面,这样线程按照一定的顺序申请释放锁称为线程同步

##条件变量

当一个线程互斥地访问某个变量时,它可能发现在其他线程改变状态之前,什么也做不了。例如一个线程访问队列时,发现队列为空,它只能等待,直到其他线程将一个节点添加到队列中。这种情况就需要用到条件变量。

##同步概念与竞态条件

🖊同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,就叫做同步。

🖊竞态条件:因为时序问题,而导致程序异常,我们称之为竞态条件。在线程场景下,这种问题也不难理解。

##条件变量函数 初始化

这些接口和mutex接口非常相似,都是属于POSIX标准的。 

int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t*restrictattr);

参数:

        cond:要初始化的条件变量

        attr:nullptr

销毁

int pthread_cond_destroy(pthread_cond_t * cond)

等待条件满足

int pthread_cond_wait(pthread_cond_t * restrict cond,pthread_mutex_t *restrict mutex);

参数:

        cond:要在这个条件变量上等待

        mutex:互斥量

唤醒等待

int pthread_cond_broadcast(pthread_cond_t * cond);

int pthread_cond_signal(pthread_cond_t * cond);

 谁调用条件变量,就链接到等待队列。调用signal,就从等待队列脱离,执行。

 

为什么pthread_cond_wait需要互斥量?

🖊条件等待是线程间同步的一种手段,如果只有一个线程,条件不满足,一直等下去都不会满足,所有必须要有一个线程通过某些操作,改变共享变量,使原先不满足的条件变得满足,并且友好的通知等待在条件变量上的线程。

🖊条件不会无缘无故地突然变得满足了,必然会牵扯到共享数据的变化。所以一定要用互斥锁来保护。没有互斥锁就无法安全地获取和修改共享数据。

测试使用:

 线程1或线程2加锁后,先进入条件变量wait,主线程每隔2s唤醒一次在cond等待队列中的线程,然后执行打印,解锁。

 线程1和线程2显示了明显的顺序性。

pthread_cond_timewait();它可以设定一个时间,在时间段内特定地阻塞式等待。时间片到了自动解除阻塞。

int pthread_cond_broadcast(pthread_cond_t * cond);这个接口唤醒一批线程,在cond条件下等待的所有线程都会被唤醒。

int pthread_cond_signal(pthread_cond_t * cond);唤醒一个线程

演示唤醒一批线程:

 

 

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

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

相关文章

HTML 标签的学习

1.HTML 的结构 前端三剑客: HTML CSS JS,本章我们学习的是HTML HTML > 超文本标记语言 HTML代码是由"标签"构成的. 形如 <body>hello</body>标签名 (body) 放到 < > 中大部分标签成对出现. 为开始标签, 为结束标签.少数标签只有开始标签…

Windows系统图标混乱(丢失)

1. 进入文件夹选项设置显示隐藏系统文件 2. &#xff08; Win7&#xff09;进入 C:\\Users\\%username%\\AppData\\Local 删除&#xff1a;IconCache.db (Win10/Win8.1) 进入 C:\\Users\\%username%\\AppData\\Local\\Microsoft\\Windows\\Explorer\\ 删除一系列iconcache…

网络协议——STP协议是什么?是如何实现的?

作者&#xff1a;Insist-- 个人主页&#xff1a;insist--个人主页 作者会持续更新网络知识和python基础知识&#xff0c;期待你的关注 目录 一、STP协议是什么 二、为什么需要STP协议 三、STP的实现过程 ​编辑 1、选举跟桥 2、给非跟桥交换机选举跟端口 3、给每个网段选…

高性能计算究竟是不是好的职业方向?

你只恍惚听人说高性能计算小众&#xff0c;你却不知道前因后果 你只仿佛听人说高性能计算很难&#xff0c;你却不曾尝试过并行 你只知道国内高性能计算就业机会不算很多&#xff0c;你却不知道国外早已如火如荼 你不知道自己的路怎么走&#xff0c;却忘记该看看别人的路怎么…

活动预告 | 中国数据库联盟(ACDU)中国行定档深圳,一起揭秘数据库前沿技术

在当今数字化时代&#xff0c;数据库是各行各业中最核心的信息管理系统之一。随着技术的飞速发展&#xff0c;数据库领域也不断涌现出新的前沿技术和创新应用。数据库运维和开发人员需要紧跟前沿技术&#xff0c;才能保持竞争力&#xff0c;并实现更高效、更智能、更人性化的应…

oGSP运维服务分论坛精彩回顾 | openGauss Developer Day 2023

5月26日&#xff0c;以“聚数成峰&#xff0c;共赢未来”为主题的openGauss Developer Day 2023在北京举办 &#xff0c;oGSP&#xff08;oGSP全称为openGauss Service Partner&#xff09;运维服务分论坛作为大会重要环节&#xff0c;也在26日下午成功举行。 出席此次活动的领…

跨境电商独立站搭建-跨境电商源码网站开发部署,独立站技术

跨境电商独立站是指在国际互联网上建立并拥有自己独立的电商网站&#xff0c;在该网站上进行跨境电商业务&#xff0c;包括产品展示、交易处理、支付结算、物流配送等全流程。相较于在第三方平台上开店&#xff0c;跨境电商独立站具有更高的自主权和品牌形象&#xff0c;能够更…

Tomcat优化

目录 Tomcat 优化Tomcat 配置文件参数优化内核优化Tomcat JVM优化 Tomcat 优化 Tomcat默认安装下的缺省配置并不适合生产环境&#xff0c;它可能会频繁出现假死现象需要重启&#xff0c;只有通过不断压测优化才能让它最高效率稳定的运行。 优化主要包括三方面&#xff0c;分别…

安装 linux系统–Ubuntu 20.04(实体机安装)

安装 linux系统–Ubuntu 20.04(实体机安装) 一、刻录操作系统镜像到U盘 使用Rufus刻录软件进行刻录&#xff0c;采用默认设置&#xff0c;以ISO模式写入等 二、开机F12(我是dell笔记本&#xff0c;其他笔记本自行百度相应选项)&#xff0c;进入Boot Options选项 选择开机启动…

「少即是多」商业策略的背后,隐藏着怎样的客户洞察?

奉行极简主义的年轻人们口中总是叨念这样一句话&#xff1a; “Less is more&#xff08;少即是多&#xff09;。” 它最早是由德国现代建筑大师路密斯凡德罗提出的一种设计理念&#xff0c;而后逐渐演变成一种生活方式&#xff0c;甚至一种商业策略。 比如&#xff0c;知名零售…

【业务功能篇17】Springboot +shedlock锁 实现定时任务

业务场景&#xff1a;我们在业务开发过程时&#xff0c;有时需要用到一些定时功能&#xff0c;定期的执行一些数据处理&#xff0c;比如每天固定时间去执行数据&#xff0c;判断是否有符合逻辑的情况&#xff0c;就生成一个告警单&#xff0c;提供给业务查看。 这里接着上一篇技…

async函数用法

目录 1.概念 2.本质 3.语法 4.特点 5.async基本使用 6.async里的await普通函数返回值 7.async里的await Promise函数成功返回值 8.async里的await Promise函数失败返回值 9.解决async里的await Promise函数失败后不执行下面内容 1.概念 真正意义上解决异步回调的问题&am…

okta/sf平台实现saml2.0单点登录集成实战(详细步骤+完整代码)

目录 第一步&#xff1a;注册okta账号 第二步&#xff1a;配置okta应用信息 第三步&#xff1a;下载Idp.xml文件 第四步&#xff1a;okta特定配置 第五步&#xff1a;集成测试 基于SuccessFactors的单点登录实现 Github工程代码链接 小结 笔者以前写过一篇关于saml2.0单…

Barra模型因子的构建及应用系列六之Book-to-Price因子

一、摘要 在前期的Barra模型系列文章中&#xff0c;我们构建了Size因子、Beta因子、Momentum因子、Residual Volatility因子和NonLinear Size因子&#xff0c;并分别创建了对应的单因子策略&#xff0c;其中Size因子和NonLinear Siz因子具有很强的收益能力。本节文章将在该系列…

AI 协助办公 |记一次用 GPT-4 写一个消息同步 App

GPT-4 最近风头正劲&#xff0c;作为 NebulaGraph 的研发人员的我自然是跟进新技术步伐。恰好&#xff0c;现在有一个将 Slack channel 消息同步到其他 IM 的需求&#xff0c;看看 GPT-4 能不能帮我完成这次的信息同步工具的代码编写工作。 本文记录了我同 GPT 主要的交互过程…

不定积分练习

不定积分练习 在看视频的时候遇到了一道比较有趣的题&#xff0c;在这里给大家分享一下。 题目 计算 ∫ ( 1 x − 1 x ) e x 1 x d x \int(1x-\dfrac 1x)e^{x\frac 1x}dx ∫(1x−x1​)exx1​dx 解&#xff1a; \qquad 原式 ∫ e x 1 x d x ∫ x ( 1 − 1 x 2 ) e x 1…

ESP Certificate Bundle 分享

基本概念 数字签名&#xff1a;是一种将相当于现实世界中的盖章、签字的功能在计算机世界中进行实现的技术。使用数字签名可以识别篡改和伪装&#xff0c;还可以防止否认。 证书&#xff1a;要开车得先靠驾照&#xff0c;驾照上面记有本人的照片、姓名、出生日期等个人信息&a…

【大唐杯学习超快速入门】5G智能网络优化

这里写目录标题 学习--实验背景上下行速率VOLTE掉话率优化时延优化 接入保持特性提升无线接通率切换成功率 附录 数据分析其他几种选项差不多 学习–实验背景 观看视频进行学习&#xff0c;理解该仿真的内涵 使用人工智能代替人工分析&#xff0c;对5G网络进行优化 上下行速率…

Centos Linux 操作系统中配置Gitlab服务器

基本准备 安装常用的工具包 linux根据操作系统的不同&#xff0c;有不同的安装工具&#xff0c;如&#xff0c; 操作系统 格式 工具 Debian .deb apt, apt-cache, apt-get, dpkg Ubuntu .deb apt, apt-cache, apt-get, dpkg CentOS .rpm yum Fedora .rpm dnf …

企业数智底座白皮书:详解数智平台必备的六大能力

在以“升级企业数智化底座”为主题的2023用友BIP技术大会上&#xff0c;用友联合全球权威咨询机构IDC共同发布《建设数字中国 升级数智底座——企业数智化底座白皮书》&#xff0c;在这本数智平台白皮书里详细解读了企业数智平台应该具备的六大基础能力。 当前企业数智化能力进…