返回顶部
首页 > 资讯 > 后端开发 > Python >Python 模拟死锁的常见实例详解
  • 230
分享到

Python 模拟死锁的常见实例详解

2024-04-02 19:04:59 230人浏览 安东尼

Python 官方文档:入门教程 => 点击学习

摘要

目录前言模拟死锁1:线程等待本身模拟死锁2:线程互相等待模拟死锁3:以错误的顺序获取锁模拟死锁4:锁未释放总结前言 常见的例子是在银行账户上:假如要在两个银行账户之间执行交易,你必须

前言

常见的例子是在银行账户上:假如要在两个银行账户之间执行交易,你必须确保两个账户都被锁定,不受其他交易的影响,以达到正确的资金转移量。在这里,这个类比并不完全成立--哲学家对应的是锁定账户的交易(分叉)--但同样的技术困难也会出现。

其他的例子包括电商秒杀系统,多个用户抢一个商品,不允许一个数据库被多个客户同时修改。

死锁也是由一个并发程序需要同时具备的条件来定义的,这样才会发生死锁。这些条件是由计算机科学家Edward G. Coffman, Jr .首先提出的,因此被称为 Coffman 条件。这些条件如下:

  • 至少有一个资源必须处于不可共享的状态。这意味着该资源被一个单独的进程(或线程)持有,不能被其他人访问; 在任何时间内,该资源只能被单个的进程(或线程)访问和持有。这个条件也被称为相互排斥。
  • 有一个进程(或线程)同时访问一个资源并等待其他进程(或线程)持有的另一个资源。换句话说,这个进程(或线程)需要访问两个资源来执行其指令,其中一个它已经持有,另一个它正在等待其他进程(或线程)。这种情况被称为保持和等待。
  • 只有在有特定指令让进程(或线程)释放资源的情况下,才能由持有这些资源的进程(或线程)来释放。这就是说,除非进程(或线程)自愿主动地释放资源,否则该资源仍处于不可共享的状态。这就是无抢占条件。
  • 最后一个条件叫做循环等待。顾名思义,这个条件规定了一组进程(或线程)的存在,因此这组进程中的第一个进程(或线程)正在等待第二个进程(或线程)释放资源,而第二个进程(或线程)又需要等待第三个进程(或线程);最后,这组进程中的最后一个进程(或线程)正在等待第一个进程。

造成线程死锁的常见例子包括:

  • 一个在自己身上等待的线程(例如,试图两次获得同一个互斥锁)
  • 互相等待的线程(例如,A 等待 B,B 等待 A)
  • 未能释放资源的线程(例如,互斥锁、信号量、屏障、条件、事件等)
  • 线程以不同的顺序获取互斥锁(例如,未能执行锁排序

模拟死锁1:线程等待本身

导致死锁的一个常见原因是线程在自己身上等待。

我们并不打算让这种死锁发生,例如,我们不会故意写代码,导致线程自己等待。相反,由于一系列的函数调用和变量的传递,这种情况会意外地发生。

一个线程可能会因为很多原因而在自己身上等待,比如:

  • 等待获得它已经获得的互斥锁
  • 等待自己被通知一个条件
  • 等待一个事件被自己设置
  • 等待一个信号被自己释放

开发一个 task() 函数,直接尝试两次获取同一个 mutex 锁。也就是说,该任务将获取锁,然后再次尝试获取锁。

# task to be executed in a new thread
def task(lock):
    print('Thread acquiring lock...')
    with lock:
        print('Thread acquiring lock again...')
        with lock:
            # will never get here
            pass

这将导致死锁,因为线程已经持有该锁,并将永远等待自己释放该锁,以便它能再次获得该锁, task() 试图两次获取同一个锁并触发死锁。

在主线程中,可以创建锁:

# create the mutex lock
lock = Lock()

然后我们将创建并配置一个新的线程,在一个新的线程中执行我们的 task() 函数,然后启动这个线程并等待它终止,而它永远不会终止。

# create and configure the new thread
thread = Thread(target=task, args=(lock,))
# start the new thread
thread.start()
# wait for threads to exit...
thread.join()

完整代码如下:

from threading import Thread
from threading import Lock
# task to be executed in a new thread
def task(lock):
    print('Thread acquiring lock...')
    with lock:
        print('Thread acquiring lock again...')
        with lock:
            # will never get here
            pass
# create the mutex lock
lock = Lock()
# create and configure the new thread
thread = Thread(target=task, args=(lock,))
# start the new thread
thread.start()
# wait for threads to exit...
thread.join()

运行结果如下:

首先创建锁,然后新的线程被混淆并启动,主线程阻塞,直到新线程终止,但它从未这样做。

新线程运行并首先获得了锁。然后它试图再次获得相同的互斥锁并阻塞。

它将永远阻塞,等待锁被释放。该锁不能被释放,因为该线程已经持有该锁。因此,该线程已经陷入死锁。

该程序必须被强制终止,例如,通过 Control-C 杀死终端。

模拟死锁2:线程互相等待

一个常见的例子就是两个或多个线程互相等待。例如:线程 A 等待线程 B,线程 B 等待线程 A。

如果有三个线程,可能会出现线程循环等待,例如:

  • 线程 A:等待线程 B
  • 线程 B:等待线程 C
  • 线程 C:等待线程 A

如果你设置了线程来等待其他线程的结果,这种死锁是很常见的,比如在一个流水线或工作流中,子任务的一些依赖关系是不符合顺序的。

from threading import current_thread
from threading import Thread
# task to be executed in a new thread
def task(other):
    # message
    print(f'[{current_thread().name}] waiting on [{other.name}]...\n')
    other.join()
# get the current thread
main_thread = current_thread()
# create the second thread
new_thread = Thread(target=task, args=(main_thread,))
# start the new thread
new_thread.start()
# run the first thread
task(new_thread)

首先得到主线程的实例 main_thread,然后创建一个新的线程 new_thread,并调用传递给主线程的 task() 函数。新线程返回一条信息并等待主线程停止,主线程用新线程的实例调用 task()函数,并等待新线程的终止。每个线程都在等待另一个线程终止,然后自己才能终止,这导致了一个死锁。

运行结果:

[Thread-1] waiting on [MainThread]...
[MainThread] waiting on [Thread-1]...

模拟死锁3:以错误的顺序获取锁

导致死锁的一个常见原因是,两个线程同时以不同的顺序获得锁。例如,我们可能有一个受锁保护的关键部分,在这个关键部分中,我们可能有代码或函数调用受第二个锁保护。

可能会遇到这样的情况:一个线程获得了锁 1 ,然后试图获得锁 2,然后有第二个线程调用获得锁 2 的功能,然后试图获得锁 1。如果这种情况同时发生,线程 1 持有锁 1,线程 2 持有锁 2,那么就会有一个死锁。

  • 线程1: 持有锁 1, 等待锁 2
  • 线程2 : 持有锁 2, 等待锁 1
from time import sleep
from threading import Thread
from threading import Lock
# task to be executed in a new thread
def task(number, lock1, lock2):
    # acquire the first lock
    print(f'Thread {number} acquiring lock 1...')
    with lock1:
        # wait a moment
        sleep(1)
        # acquire the next lock
        print(f'Thread {number} acquiring lock 2...')
        with lock2:
            # never gets here..
            pass
# create the mutex locks
lock1 = Lock()
lock2 = Lock()
# create and configure the new threads
thread1 = Thread(target=task, args=(1, lock1, lock2))
thread2 = Thread(target=task, args=(2, lock2, lock1))
# start the new threads
thread1.start()
thread2.start()
# wait for threads to exit...
thread1.join()
thread2.join()

运行这个例子首先创建了两个锁。然后两个线程都被创建,主线程等待线程的终止。

第一个线程接收 lock1 和 lock2 作为参数。它获得了锁 1 并 sleep。

第二个线程接收 lock2 和 lock1 作为参数。它获得了锁 2 并 sleep。

第一个线程醒来并试图获取锁 2,但它必须等待,因为它已经被第二个线程获取。第二个线程醒来并试图获取锁 1,但它必须等待,因为它已经被第一个线程获取。

结果是一个死锁:

Thread 1 acquiring lock 1...
Thread 2 acquiring lock 1...
Thread 1 acquiring lock 2...
Thread 2 acquiring lock 2...

解决办法是确保锁在整个程序中总是以相同的顺序获得。这就是所谓的锁排序。

模拟死锁4:锁未释放

导致死锁的另一个常见原因是线程未能释放一个资源。这通常是由线程在关键部分引发错误或异常造成的,这种方式会阻止线程释放资源,包括:

  • 未能释放一个锁
  • 未能释放一个信号器
  • 未能到达一个 barrier
  • 未能在一个条件上通知线程
  • 未能设置一个事件
# example of a deadlock caused by a thread failing to release a lock
from time import sleep
from threading import Thread
from threading import Lock
# task to be executed in a new thread
def task(lock):
    # acquire the lock
    print('Thread acquiring lock...')
    lock.acquire()
    # fail
    raise Exception('Something bad happened')
    # release the lock (never gets here)
    print('Thread releasing lock...')
    lock.release()
# create the mutex lock
lock = Lock()
# create and configure the new thread
thread = Thread(target=task, args=(lock,))
# start the new thread
thread.start()
# wait a while
sleep(1)
# acquire the lock
print('Main acquiring lock...')
lock.acquire()
# do something...
# release lock (never gets here)
lock.release()

运行该例子时,首先创建锁,然后创建并启动新的线程。然后主线程阻塞。新线程运行。它首先获得了锁,然后引发了一个异常而失败。该线程解开了锁,但却没有解开锁的代码。新的线程终止了。最后,主线程被唤醒,然后试图获取锁。由于锁没有被释放,主线程永远阻塞,导致了死锁。

Thread acquiring lock...
Exception in thread Thread-1:
Traceback (most recent call last):
  ...
Exception: Something bad happened
Main acquiring lock...

总结

本文首先通过实际案例中可能出现死锁的情况,介绍了死锁的概念及条件,并通过 python 代码模拟死锁的四种情况,更多关于Python 模拟死锁的资料请关注编程网其它相关文章!

--结束END--

本文标题: Python 模拟死锁的常见实例详解

本文链接: https://lsjlt.com/news/120245.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Python 模拟死锁的常见实例详解
    目录前言模拟死锁1:线程等待本身模拟死锁2:线程互相等待模拟死锁3:以错误的顺序获取锁模拟死锁4:锁未释放总结前言 常见的例子是在银行账户上:假如要在两个银行账户之间执行交易,你必须...
    99+
    2024-04-02
  • 详解Golang并发操作中常见的死锁情形
    目录第一种情形:无缓存能力的管道,自己写完自己读 第二种情形:协程来晚了 第三种情形:管道读写时,相互要求对方先读/写 第四种情形:读写锁相互阻塞,形成隐形死锁 什么是死锁,在Go的...
    99+
    2024-04-02
  • MySQL死锁的案例详解
    本篇内容介绍了“MySQL死锁的案例详解”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成! ...
    99+
    2024-04-02
  • iOS中的线程死锁实例详解
    什么是线程死锁 是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。 线程死锁怎么发生 发生死锁的情况一般是两个对象的锁相...
    99+
    2022-05-15
    ios 线程 死锁
  • java 中死锁问题的实例详解
    java 中死锁问题的实例详解先看代码在做解释public class DeadLock implements Runnable{ String a; String b; boolean flag; public DeadLock(...
    99+
    2023-05-31
    java 死锁 ava
  • java多线程学习之死锁的模拟和避免(实例讲解)
    1.死锁死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。Java 死锁产生的四个必要条件:互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用不可抢...
    99+
    2023-05-31
    java 多线程 避免
  • Python 中的json常见用法实例详解
    目录导包api介绍常见用法json转python内置对象字典对象数组对象文件读取python内置对象转json字典转json 字典转json(压缩存储) 字典转j...
    99+
    2022-12-28
    python json用法 python json Python json常见用法
  • Python模拟百度登录实例详解
    最近公司产品和百度贴吧合作搞活动,为了增加人气,打算做个自动签到的小程序。这个是测试登录的代码,写的比较随意,仅实现了登录并读取关注贴吧列表,下边的就比较简单。 百度登录还是有点麻烦的,由于用的ssl,所以...
    99+
    2022-06-04
    详解 实例 Python
  • 一文详解Python中实现单例模式的几种常见方式
    目录Python 中实现单例模式的几种常见方式元类(Metaclass):装饰器(Decorator):模块(Module):new 方法:Python 中实现单例模式的几种常见方式...
    99+
    2023-03-22
    Python 单例模式 Python 单例
  • Python常见文件操作的示例详解
    目录从文件中读取数据为什么要提供文件路径逐行读取创建一个包含文件各行内容的列表使用文件中的内容包含千位以上的大型文件圆周率中包含你的生日吗写入文件附加到文件从文件中读取数据 1:读取...
    99+
    2024-04-02
  • Node.js Webpack常见的模式详解
    目录 一、认识插件 Plugin认识Plugin二、CleanWebpackPlugin三、HtmlWebpackPlugin生成index.html分析自定义HTML模版...
    99+
    2022-11-13
    Node.js Webpack模式 Node.js Webpack
  • Oracle常见死锁发生的原因以及解决方法
    一.删除和更新之间引起的死锁 造成死锁的原因就是多个线程或进程对同一个资源的争抢或相互依赖。这里列举一个对同一个资源的争抢造成死锁的实例。 CREATE ...
    99+
    2024-04-02
  • 举例讲解Python中的死锁、可重入锁和互斥锁
    一、死锁 简单来说,死锁是一个资源被多次调用,而多次调用方都未能释放该资源就会造成死锁,这里结合例子说明下两种常见的死锁情况。 1、迭代死锁 该情况是一个线程“迭代”请求同一个资源,直接就会造成死锁: ...
    99+
    2022-06-04
    死锁 互斥 Python
  • C++中vector的模拟实现实例详解
    目录vector接口总览 默认成员函数 构造函数 拷贝构造 赋值重载 析构函数 迭代器相关函数 begin和end 容量相关函数 size和capacity reserve resi...
    99+
    2024-04-02
  • Oracle常见分析函数实例详解
    目录1. 认识分析函数1.1 什么是分析函数1.2 分析函数和聚合函数的不同1.3 分析函数的形式2. 理解over()函数2.1 两个order by 的执行机制2.2 分析函数中的分组、排序、窗口2.3 帮助理解ov...
    99+
    2023-04-25
    oracle分析函数用法 oracle的分析函数 oracle分析函数有哪些
  • 详解使用IDEA模拟git命令使用的常见场景
    大家好,最近白泽第一次开始参与小组合作开发,以前都是自己用git保存自己的代码,自己维护,用git的场景也比较单一,没有遇到过拉取代码合并出现冲突的问题。但是小组开发拉取远程仓库的代...
    99+
    2024-04-02
  • Python单例模式实例详解
    本文实例讲述了Python单例模式。分享给大家供大家参考,具体如下: 单例模式:保证一个类仅有一个实例,并提供一个访问他的全局访问点。 实现某个类只有一个实例的途径: 1,让一个全局变量使得一个对象被访问,...
    99+
    2022-06-04
    详解 实例 模式
  • 常见的python正则用法实例讲解
    下面列出Python正则表达式的几种匹配用法: 此外,关于正则的一切http://deerchao.net/tutorials/regex/regex.htm 1.测试正则表达式是否匹配字符串的全部或部分...
    99+
    2022-06-04
    正则 实例 常见
  • Python爬虫利用cookie实现模拟登陆实例详解
    Cookie,指某些网站为了辨别用户身份、进行session跟踪而储存在用户本地终端上的数据(通常经过加密)。 举个例子,某些网站是需要登录后才能得到你想要的信息的,不登陆只能是游客模式,那么我们可以利用U...
    99+
    2022-06-04
    爬虫 详解 实例
  • 如何解决 C++ 多线程编程中常见的死锁问题?
    如何解决 c++++ 多线程编程中的常见死锁问题?避免死锁的技术:加锁顺序:始终以相同的顺序获取锁。死锁检测:使用算法检测并解决死锁。超时:为锁设置超时值,防止线程无限期等待。优先级反转...
    99+
    2024-05-13
    多线程编程 死锁 c++
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作