首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >多进程+异步的SystemExit死锁

多进程+异步的SystemExit死锁
EN

Stack Overflow用户
提问于 2022-02-23 12:28:41
回答 1查看 192关注 0票数 1

以下代码死锁,而不是在异步任务中引发SystemExit时退出。

代码语言:javascript
运行
复制
import asyncio
import multiprocessing as mp

def worker_main(pipe):
    try:
        print("Worker started")
        pipe.recv()
    finally:
        print("Worker exiting.")

async def main():
    _, other_end = mp.Pipe()
    worker = mp.Process(target=worker_main, args=(other_end,))
    worker.daemon = False # (default), True causes multiprocessing explicitly to terminate worker
    worker.start()
    await asyncio.sleep(2)
    print("Main task raising SystemExit")
    raise SystemExit

if __name__ == "__main__":
    asyncio.get_event_loop().run_until_complete(main())

它只打印以下内容,然后挂起:

代码语言:javascript
运行
复制
Worker started
Main task raising SystemExit

如果然后按Ctrl+C,就会得到一个跟踪,指示它在multiprocessing.util._exit_function()中等待p.join()

这是在Windows 10上使用Python3.9.5。

从表面上看,原因如下:

在异步任务中引发SystemExit时,似乎所有任务都已结束,然后在事件循环之外重新引发SystemExit。多处理库已向multiprocessing.util._exit_function()注册了atexit.register(),并被调用为主进程退出。这在辅助进程上执行p.join()。关键的是,这发生在管道关闭之前,因此,当主进程等待工作人员退出时,它会与等待管道关闭(EOFError)的工作人员死锁。

解决方案/解决方法似乎是使工作人员成为一个守护进程,以便_exit_function()在打破死锁的p.join()之前显式地终止它。唯一的问题是它阻止工人在退出前进行任何清理。

在非异步应用程序中也不会出现同样的问题,我不知道为什么会有所不同。如果应用程序是非异步的,那么当主进程退出时,工作人员的管道就会中断,并且工作人员将按预期的方式退出EOFError。我还确认了,如果允许异步任务正常退出,但在run_until_complete()返回后有一个run_until_complete(),那么它的行为与非异步情况一样--也就是说,它正确地退出。

这是Python中的一个bug,还是预期它会这样做?

EN

Stack Overflow用户

发布于 2022-02-24 05:19:12

Python3.10.0 + Win10具有相同的行为。

,这是Python中的一个bug,还是预期它会这样做?

问得好。在我看来,这不是Python中的一个bug。您有一个具有单个任务和一个线程的异步程序。启动事件循环后,主线程中的所有代码都在事件循环中运行。当它执行p.join()时,它是一个阻塞调用。因为没有第二个线程可以导致取消块,所以程序就挂起了。

我的理解是,异步程序中的每个任务在(可能)传播异常(通常是到asyncio.run()调用)之前都会处理自己的异常。(至少这是我的经验,很难看出这是怎么回事。)因此,同步程序和异步程序之间的异常处理环境是不同的。

第二点: Python不可能知道如何优雅地终止非守护进程。如果将进程指定为守护进程,则实际上是告诉Python进程可以安全终止。然而,正如您所指出的,这将绕过任何所需的清理。

因为您有一个非守护进程,所以要由您的代码来执行清理。

第三点:如果您替换这一行:

代码语言:javascript
运行
复制
raise SystemExit

有了这个:

代码语言:javascript
运行
复制
raise Exception

同样的问题也会发生,原因也是一样的。程序不会退出,因为有一个非守护进程正在运行。这个问题不仅是SystemExit的清理,也是任何例外。如果你解决了这个问题,你的问题就没有意义了。

您可以在主函数中显式捕获SystemExit和异常,如下所示:

代码语言:javascript
运行
复制
import asyncio
import multiprocessing as mp

def worker_main(pipe):
    try:
        print("Worker started")
        pipe.recv()
    finally:
        print("Worker exiting.")

async def main():
    my_end, other_end = mp.Pipe()
    worker = mp.Process(target=worker_main, args=(other_end,))
    try:
        worker.daemon = False 
        worker.start()
        await asyncio.sleep(2)
        print("Main task raising SystemExit")
        raise SystemExit
        # raise Exception
    except (SystemExit, Exception):
        my_end.send("Bye")
        worker.join()
        raise

if __name__ == "__main__":
    asyncio.run(main())

通过注释掉其中一个或另一个,您可以看到SystemExit和异常之间的区别。两人都优雅地离开了。SystemExit不会将追溯打印到控制台;异常会打印。

票数 1
EN
查看全部 1 条回答
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/71236951

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档