首页 > 解决方案 > 调用异步函数时如何防止上下文切换?

问题描述

如果我使用异步函数,那么堆栈上面的所有函数也应该是异步的,并且它们的调用应该在 await 关键字之前。此示例使用应用程序的多个架构层模拟现代程序:

async def func1():
    await asyncio.sleep(1)

async def func2():
    await func1()

async def func3():
    await func2()

async def func4():
    await func3()

async def func5():
    await func4()

当一个执行线程遇到'await'时,它可以切换到另一个协程,这需要资源进行上下文切换。随着大量竞争的corutes和不同的抽象级别,这些开销可能会开始限制整个系统的性能。但是在给出的示例中,仅在一种情况下在线切换上下文是有意义的:

await asyncio.sleep(1)

如何禁止某些异步函数的上下文切换?

标签: pythonpython-3.xasynchronousasync-awaitpython-asyncio

解决方案


首先,默认情况下,在您的示例上下文中不会切换。换句话说,直到协程遇到实际阻塞的事情(例如Future),它才会将控制权返回给事件循环并直接恢复到内部协程。

我不知道比继承默认事件循环实现更简单的方法来证明这一点:

import asyncio


class TestEventLoop(asyncio.SelectorEventLoop):
    def _run_once(self):
        print('control inside event loop')
        super()._run_once()


async def func1():
    await asyncio.sleep(1)


async def func2():
    print('before func1')
    await func1()
    print('after func1')


async def main():
    print('before func2')
    await func2()
    print('after func2')


loop = TestEventLoop()
asyncio.set_event_loop(loop)
try:
    loop.run_until_complete(main())
finally:
    loop.close()

在输出中你会看到:

control inside event loop
before func2
before func1
control inside event loop
control inside event loop
after func1
after func2
control inside event loop

func2直接传递执行流程以避免可能切换到另一个协程func1的事件循环。_run_once只有在asyncio.sleep遇到阻塞时,事件循环才能获得控制权。

虽然这是默认事件循环的实现细节。


其次,可能更重要的是,与使用 asyncio 处理 I/O 相比,协程之间的切换非常便宜。

它也比其他异步替代方案(如在 OS 线程之间切换)便宜得多。

由于许多协程而导致代码变慢的情况极不可能发生,但即使发生这种情况,您也应该看看更有效的事件循环实现,例如uvloop


推荐阅读