Python 多线程 - 同步、互斥锁、死锁、银行家算法

2024-08-21 04:58

本文主要是介绍Python 多线程 - 同步、互斥锁、死锁、银行家算法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

仅用学习参考

同步的概念

同步就是协同步调,按预定的先后次序进行运行。如:你说完,我再说。

"同"字从字面上容易理解为一起动作

其实不是,"同"字应是指协同、协助、互相配合。

如进程、线程同步,可理解为进程或线程A和B一块配合,A执行到一定程度时要依靠B的某个结果,于是停下来,示意B运行;B执行,再将结果给A;A再继续操作。

解决线程同时修改全局变量的方式
对于上次提出的那个计算错误的问题,可以通过线程同步来进行解决

思路,如下:

系统调用t1,然后获取到g_num的值为0,此时上一把锁,即不允许其他线程操作g_num
t1对g_num的值进行+1
t1解锁,此时g_num的值为1,其他的线程就可以使用g_num了,而且是g_num的值不是0而是1
同理其他线程在对g_num进行修改时,都要先上锁,处理完后再解锁,在上锁的整个过程中不允许其他线程访问,就保证了数据的正确性

互斥锁

当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制

线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。

互斥锁为资源引入一个状态:锁定/非锁定

某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。

13423234-8d0fed419424085d.png

threading模块中定义了Lock类,可以方便的处理锁定:

创建锁

mutex = threading.Lock()

锁定

mutex.acquire()

释放

mutex.release()

注意:
如果这个锁之前是没有上锁的,那么acquire不会堵塞
如果在调用acquire对这个锁上锁之前 它已经被 其他线程上了锁,那么此时acquire会堵塞,直到这个锁被解锁为止

使用互斥锁完成2个线程对同一个全局变量各加1000万次的操作

[root@server01 many_task]# vim test8.py #coding=utf-8
import threading
import timeg_num = 0# 创建一个互斥锁
# 默认是未上锁的状态
mutex = threading.Lock()def work1(num):global g_numfor i in range(num):mutex.acquire() # 上锁g_num += 1mutex.release() # 解锁 print("----in work1, g_num is %d---"%g_num)def work2(num):global g_numfor i in range(num):mutex.acquire() # 上锁g_num += 1mutex.release() # 解锁print("----in work2, g_num is %d---"%g_num)def main():print("---线程创建之前g_num is %d---"%g_num)# 创建两个线程,各自对g_num进行相加t1 = threading.Thread(target=work1, args=(10000000,))t1.start()t2 = threading.Thread(target=work2, args=(10000000,))t2.start()while len(threading.enumerate()) != 1:time.sleep(1)print("2个线程对同一个全局变量操作之后的最终结果是:%s" % g_num)if __name__ == "__main__":main()

运行结果如下:

[root@server01 many_task]# python test8.py 
---线程创建之前g_num is 0---
----in work2, g_num is 19724895---
----in work1, g_num is 20000000---
2个线程对同一个全局变量操作之后的最终结果是:20000000
[root@server01 many_task]# 

可以看到最后的结果,加入互斥锁后,其结果与预期相符。

上锁解锁过程
当一个线程调用锁的acquire()方法获得锁时,锁就进入“locked”状态。

每次只有一个线程可以获得锁。如果此时另一个线程试图获得这个锁,该线程就会变为“blocked”状态,称为“阻塞”,直到拥有锁的线程调用锁的release()方法释放锁之后,锁进入“unlocked”状态。

线程调度程序从处于同步阻塞状态的线程中选择一个来获得锁,并使得该线程进入运行(running)状态。

总结

锁的好处:

确保了某段关键代码只能由一个线程从头到尾完整地执行
锁的坏处:

阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了
由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁

死锁

现实社会中,男女双方都在等待对方先道歉

13423234-ac58ef0e900d4abd.png

如果双方都这样固执的等待对方先开口,弄不好,就分手了。

1. 死锁

在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。

尽管死锁很少发生,但一旦发生就会造成应用的停止响应。下面看一个死锁的例子:

[root@server01 many_task]# vim test9.py #coding=utf-8
import threading
import time# 初始化两个锁
mutexA = threading.Lock()
mutexB = threading.Lock()class MyThread1(threading.Thread):def run(self):# 对mutexA上锁mutexA.acquire()# mutexA上锁后,延时1秒,等待另外一个线程。此时把mutexB上锁print(self.name + '-------do1-------up-------')time.sleep(1)# 此时会堵塞,因为这个mutexB已经被另外一个线程优先上锁了mutexB.acquire()print(self.name + '-------do1-------down-----')mutexB.release()# 对mutexA解锁,此时mutexB已经被堵塞,无法执行到这里的,那么就会锁住mutexA.release()class MyThread2(threading.Thread):def run(self):# 对mutexB上锁mutexB.acquire()# mutexB上锁后,延时1秒,等待另外一个线程。此时把mutexA上锁print(self.name + '-------do2-------up-------')time.sleep(1)# 此时会堵塞,因为这个mutexA已经被另外一个线程优先上锁了mutexA.acquire()print(self.name + '-------do-------down-----')mutexA.release()# 对mutexB解锁,此时mutexA已经被堵塞,无法执行到这里的,那么就会锁住mutexB.release()def main():t1 = MyThread1()t2 = MyThread2()# 开启两个线程,此时两个线程会相互堵塞t1.start()t2.start()if __name__ == "__main__":main()

运行结果如下:

[root@server01 many_task]# python test9.py 
Thread-1-------do1-------up-------
Thread-2-------do2-------up-------

两个锁在两个进程之中直接相互堵塞,导致第二个print语句无法执行打印。
那么该如何避免死锁呢?

2. 避免死锁

程序设计时要尽量避免(银行家算法)
添加超时时间等

银行家算法

背景知识

一个银行家如何将一定数目的资金安全地借给若干个客户,使这些客户既能借到钱完成要干的事,同时银行家又能收回全部资金而不至于破产,这就是银行家问题。这个问题同操作系统中资源分配问题十分相似:银行家就像一个操作系统,客户就像运行的进程,银行家的资金就是系统的资源。

问题的描述

一个银行家拥有一定数量的资金,有若干个客户要贷款。每个客户须在一开始就声明他所需贷款的总额。若该客户贷款总额不超过银行家的资金总数,银行家可以接收客户的要求。客户贷款是以每次一个资金单位(如1万RMB等)的方式进行的,客户在借满所需的全部单位款额之前可能会等待,但银行家须保证这种等待是有限的,可完成的。

例如:有三个客户C1,C2,C3,向银行家借款,该银行家的资金总额为10个资金单位,其中C1客户要借9各资金单位,C2客户要借3个资金单位,C3客户要借8个资金单位,总计20个资金单位。某一时刻的状态如图所示。

13423234-dc5fa0de12563925.png

对于a图的状态,按照安全序列的要求,我们选的第一个客户应满足该客户所需的贷款小于等于银行家当前所剩余的钱款,可以看出只有C2客户能被满足:C2客户需1个资金单位,小银行家手中的2个资金单位,于是银行家把1个资金单位借给C2客户,使之完成工作并归还所借的3个资金单位的钱,进入b图。同理,银行家把4个资金单位借给C3客户,使其完成工作,在c图中,只剩一个客户C1,它需7个资金单位,这时银行家有8个资金单位,所以C1也能顺利借到钱并完成工作。最后(见图d)银行家收回全部10个资金单位,保证不赔本。那麽客户序列{C1,C2,C3}就是个安全序列,按照这个序列贷款,银行家才是安全的。否则的话,若在图b状态时,银行家把手中的4个资金单位借给了C1,则出现不安全状态:这时C1,C3均不能完成工作,而银行家手中又没有钱了,系统陷入僵持局面,银行家也不能收回投资。

综上所述,银行家算法是从当前状态出发,逐个按安全序列检查各客户谁能完成其工作,然后假定其完成工作且归还全部贷款,再进而检查下一个能完成工作的客户,......。如果所有客户都能完成工作,则找到一个安全序列,银行家才是安全的。

13423234-7907ae6344e86e8a.png

关注微信公众号,回复【资料】、Python、PHP、JAVA、web,则可获得Python、PHP、JAVA、前端等视频资料。

这篇关于Python 多线程 - 同步、互斥锁、死锁、银行家算法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1092184

相关文章

Python实现终端清屏的几种方式详解

《Python实现终端清屏的几种方式详解》在使用Python进行终端交互式编程时,我们经常需要清空当前终端屏幕的内容,本文为大家整理了几种常见的实现方法,有需要的小伙伴可以参考下... 目录方法一:使用 `os` 模块调用系统命令方法二:使用 `subprocess` 模块执行命令方法三:打印多个换行符模拟

Python实现MQTT通信的示例代码

《Python实现MQTT通信的示例代码》本文主要介绍了Python实现MQTT通信的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录1. 安装paho-mqtt库‌2. 搭建MQTT代理服务器(Broker)‌‌3. pytho

基于Python开发一个图像水印批量添加工具

《基于Python开发一个图像水印批量添加工具》在当今数字化内容爆炸式增长的时代,图像版权保护已成为创作者和企业的核心需求,本方案将详细介绍一个基于PythonPIL库的工业级图像水印解决方案,有需要... 目录一、系统架构设计1.1 整体处理流程1.2 类结构设计(扩展版本)二、核心算法深入解析2.1 自

从入门到进阶讲解Python自动化Playwright实战指南

《从入门到进阶讲解Python自动化Playwright实战指南》Playwright是针对Python语言的纯自动化工具,它可以通过单个API自动执行Chromium,Firefox和WebKit... 目录Playwright 简介核心优势安装步骤观点与案例结合Playwright 核心功能从零开始学习

Python 字典 (Dictionary)使用详解

《Python字典(Dictionary)使用详解》字典是python中最重要,最常用的数据结构之一,它提供了高效的键值对存储和查找能力,:本文主要介绍Python字典(Dictionary)... 目录字典1.基本特性2.创建字典3.访问元素4.修改字典5.删除元素6.字典遍历7.字典的高级特性默认字典

Python自动化批量重命名与整理文件系统

《Python自动化批量重命名与整理文件系统》这篇文章主要为大家详细介绍了如何使用Python实现一个强大的文件批量重命名与整理工具,帮助开发者自动化这一繁琐过程,有需要的小伙伴可以了解下... 目录简介环境准备项目功能概述代码详细解析1. 导入必要的库2. 配置参数设置3. 创建日志系统4. 安全文件名处

使用Python构建一个高效的日志处理系统

《使用Python构建一个高效的日志处理系统》这篇文章主要为大家详细讲解了如何使用Python开发一个专业的日志分析工具,能够自动化处理、分析和可视化各类日志文件,大幅提升运维效率,需要的可以了解下... 目录环境准备工具功能概述完整代码实现代码深度解析1. 类设计与初始化2. 日志解析核心逻辑3. 文件处

python生成随机唯一id的几种实现方法

《python生成随机唯一id的几种实现方法》在Python中生成随机唯一ID有多种方法,根据不同的需求场景可以选择最适合的方案,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习... 目录方法 1:使用 UUID 模块(推荐)方法 2:使用 Secrets 模块(安全敏感场景)方法

使用Python删除Excel中的行列和单元格示例详解

《使用Python删除Excel中的行列和单元格示例详解》在处理Excel数据时,删除不需要的行、列或单元格是一项常见且必要的操作,本文将使用Python脚本实现对Excel表格的高效自动化处理,感兴... 目录开发环境准备使用 python 删除 Excphpel 表格中的行删除特定行删除空白行删除含指定

Python通用唯一标识符模块uuid使用案例详解

《Python通用唯一标识符模块uuid使用案例详解》Pythonuuid模块用于生成128位全局唯一标识符,支持UUID1-5版本,适用于分布式系统、数据库主键等场景,需注意隐私、碰撞概率及存储优... 目录简介核心功能1. UUID版本2. UUID属性3. 命名空间使用场景1. 生成唯一标识符2. 数