Python异步进程与Semaphore出现死锁:原因是什么?
为何这段代码会出现死锁?
以下是出现死锁的代码:
#!/usr/bin/env python3 import asyncio async def _stream_subprocess(id: int, command: "list[str]"): proc = await asyncio.create_subprocess_exec(*command, stdout=asyncio.subprocess.PIPE) await proc.wait() print(f"{id}: Done") async def run(id: int, command: "list[str]"): print(f"{id}: Running") await _stream_subprocess(id, command) print(f"{id}: Throwing") raise RuntimeError("failed") async def run_with_parallelism_limit(id: int, command: "list[str]", limit: asyncio.Semaphore): async with limit: print(f"{id}: Calling run") await run(id, command) print(f"{id}: Run finished") async def main(): sem = asyncio.Semaphore(1) await asyncio.gather( run_with_parallelism_limit(0, ["python", "--version"], sem), run_with_parallelism_limit(1, ["python", "--version"], sem), ) if __name__ == "__main__": asyncio.run(main())
程序的输出结果:
0: Calling run 0: Running 0: Done 0: Throwing 1: Calling run 1: Running
死锁原因
问题核心在_stream_subprocess函数的实现:
- 创建子进程时指定了
stdout=asyncio.subprocess.PIPE,但完全没有读取管道中的输出内容。 - 子进程尝试向stdout管道写入数据时,一旦管道缓冲区被填满,子进程会被阻塞,无法正常退出。
await proc.wait()会一直等待子进程终止,而子进程因管道阻塞无法完成退出流程,导致当前任务永久挂起,最终整个程序陷入死锁。
即使python --version输出量极小,asyncio的子进程机制中,未读取的管道也会破坏子进程的正常退出逻辑,进而触发死锁。
解决方法
有两种简单的修复思路:
方法1:读取管道内容
在等待子进程结束前,主动读取stdout管道的所有内容,避免子进程阻塞:
async def _stream_subprocess(id: int, command: "list[str]"): proc = await asyncio.create_subprocess_exec(*command, stdout=asyncio.subprocess.PIPE) # 读取stdout内容,释放管道缓冲区 await proc.stdout.read() await proc.wait() print(f"{id}: Done")
方法2:不捕获stdout
如果不需要获取子进程的输出,直接移除stdout参数,让子进程输出直接流向父进程的stdout:
async def _stream_subprocess(id: int, command: "list[str]"): proc = await asyncio.create_subprocess_exec(*command) await proc.wait() print(f"{id}: Done")
内容的提问来源于stack exchange,提问作者Timmmm
相关产品推荐
相关产品推荐

