处理异步死锁

时间:2019-03-25 15:26:10

标签: python-asyncio

此示例代码无限期挂起:

import asyncio


async def main():
    async def f():
        await g_task

    async def g():
        await f_task

    f_task = asyncio.create_task(f())
    g_task = asyncio.create_task(g())
    await f_task


asyncio.run(main())

我正在寻找一种自动检测和处理死锁的方法,就像GoLang一样。

到目前为止,我想出了asyncio.wait_for()的一种变体:

[EDIT]大修设计

https://gist.github.com/gimperiale/549cbad04c24d870145d3f38fbb8e6f0

原始代码中的1行更改:

await wait_check_deadlock(f_task)

它可以工作,但是有两个主要问题:

  1. 它依赖于asyncio.Task._fut_waiter,它是CPython的实现细节
  2. 死锁的任务将永远保留在RAM中。 aw.cancel()似乎无能为力。如果我发现辅助函数引发了RecursionError,则asyncio.run()在尝试取消所有任务时会引发另一个RecursionError。

有没有更强大的解决方案?

1 个答案:

答案 0 :(得分:1)

对避免死锁的研究已经很多,存在一些可行的解决方案,但是在一般情况下,这个问题是不确定的(我认为可以将其简化为停止问题)。

为说明实用性,请考虑以下问题:

await asyncio.sleep(2 ** (1 / random.random()))

根据您的运气,它会很快返回,或者“实际上永远不会”。

此技巧可用于表明无法预测基于回调的程序:

f = asyncio.Future()

async foo():
    await asyncio.sleep(2 ** (1 / random.random()))
    f.set_result(None)

async bar():
    await f

await asyncio.gather(foo(), bar())

同样,它可以应用于您的“纯”异步/等待程序:

async def f():
    await g_task

async def g():
    await asyncio.wait(f_task,
                       asyncio.sleep(2 ** (1 / random.random())),
                       return_when=asyncio.FIRST_COMPLETED)

f_task = asyncio.create_task(f())
g_task = asyncio.create_task(g())
await f_task

同时,不完美但实用的死锁检测器可能非常有用,请考虑将代码发布到核心asyncio开发人员和/或独立库中。

当前的做法是使用PYTHONASYNCIODEBUG=1运行测试,该测试显示未等待的任务(在读取结果/异常之前已销毁)。

您的库可能会更好,例如,它可以报告某些任务花费的时间长于X,或者取决于给定任务的任务DAG何时增长。