Python 并发编程之死锁(中)

简介: 在这一节中,我们将讨论一个思想实验,通常被称为餐饮哲学家问题,以说明死锁的概念及其原因;从这里开始,你将学习如何在 Python 并发程序中模拟这个问题。

模拟死锁 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()


运行结果如下:


image.png


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


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


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


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


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

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


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


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


image.png


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

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]...
相关文章
|
16天前
|
安全 开发者 Python
Python并发编程实践与性能优化
本文探讨了Python中并发编程的重要性及其实践方法,特别是在提升程序性能方面的应用。通过介绍多线程、多进程以及异步编程的基本概念和实现技巧,读者能够了解如何利用Python强大的并发特性来优化程序效率和响应速度。
|
1月前
|
并行计算 数据处理 Python
Python并发编程迷雾:IO密集型为何偏爱异步?CPU密集型又该如何应对?
【7月更文挑战第17天】Python并发编程中,异步编程(如`asyncio`)在IO密集型任务中提高效率,利用等待时间执行其他任务。但对CPU密集型任务,由于GIL限制,多线程效率不高,此时应选用`multiprocessing`进行多进程并行计算以突破限制。选择合适的并发策略是关键:异步适合IO,多进程适合CPU。理解这些能帮助构建高效并发程序。
33 6
|
1月前
|
开发框架 并行计算 .NET
从菜鸟到大神:Python并发编程深度剖析,IO与CPU的异步战争!
【7月更文挑战第18天】Python并发涉及多线程、多进程和异步IO(asyncio)。异步IO适合IO密集型任务,如并发HTTP请求,能避免等待提高效率。多进程在CPU密集型任务中更优,因可绕过GIL限制实现并行计算。通过正确选择并发策略,开发者能提升应用性能和响应速度。
40 3
|
1月前
|
数据处理 Python
深入探索:Python中的并发编程新纪元——协程与异步函数解析
【7月更文挑战第15天】Python 3.5+引入的协程和异步函数革新了并发编程。协程,轻量级线程,由程序控制切换,降低开销。异步函数是协程的高级形式,允许等待异步操作。通过`asyncio`库,如示例所示,能并发执行任务,提高I/O密集型任务效率,实现并发而非并行,优化CPU利用率。理解和掌握这些工具对于构建高效网络应用至关重要。
34 6
|
1月前
|
UED 开发者 Python
Python并发编程新纪元:异步编程如何重塑IO与CPU密集型任务的处理方式?
【7月更文挑战第18天】Python异步编程提升IO任务效率,非阻塞模式减少等待时间,优化用户体验。asyncio库与await关键字助力编写非阻塞代码,示例展示异步HTTP请求。CPU密集型任务中,异步编程结合多进程可提升效率。异步编程挑战包括代码复杂性,解决策略包括使用类型提示、异步框架及最佳实践。异步编程重塑任务处理方式,成为现代Python开发的关键。
26 2
|
1月前
|
数据采集 并行计算 数据处理
工具人必看:Python并发编程工具箱大揭秘,IO与CPU密集型任务的最佳拍档!
【7月更文挑战第16天】Python并发编程助力IO密集型(asyncio+aiohttp,异步Web爬虫示例)和CPU密集型(multiprocessing,并行计算数组和)任务。asyncio利用单线程异步IO提升Web应用效率,multiprocessing通过多进程克服GIL限制,实现多核并行计算。善用这些工具,可优化不同场景下的程序性能。
30 1
|
1月前
|
调度 Python
揭秘Python并发编程核心:深入理解协程与异步函数的工作原理
【7月更文挑战第15天】Python异步编程借助协程和async/await提升并发性能,减少资源消耗。协程(async def)轻量级、用户态,便于控制。事件循环,如`asyncio.get_event_loop()`,调度任务执行。异步函数内的await关键词用于协程间切换。回调和Future对象简化异步结果处理。理解这些概念能写出高效、易维护的异步代码。
25 2
|
16天前
|
数据采集 并行计算 程序员
Python中的并发编程:理解多线程与多进程
在Python编程中,理解并发编程是提升程序性能和效率的关键。本文将深入探讨Python中的多线程和多进程编程模型,比较它们的优劣势,并提供实际应用中的最佳实践与案例分析。
|
16天前
|
安全 大数据 Java
极限探索:Python中的并发编程技术
在Python编程世界中,实现高效的并发处理是追求极致性能的必经之路。本文将深入探讨Python中常用的并发编程技术,包括多线程、多进程和异步编程,揭示它们的优势、适用场景以及实现方法。通过对比分析不同技术的特点和局限性,帮助读者选择最适合其项目需求的并发策略,从而在应对大规模数据处理和高并发请求时达到最优效果。
|
30天前
|
数据采集 算法 数据处理
Python中的并发编程:异步IO与多线程对比分析
传统的多线程编程在Python中因为全局解释器锁(GIL)的存在受到限制,导致多线程并不能充分利用多核处理器的优势。本文将探讨Python中的异步IO编程与多线程编程的差异与优劣,并分析适合的应用场景。