调用异步函数时如何防止上下文切换?

时间:2019-02-25 05:57:46

标签: python python-3.x asynchronous async-await python-asyncio

如果我使用异步函数,那么堆栈上方的所有函数也应该是异步的,并且它们的调用应以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”时,它可以切换到另一个协程,这需要用于上下文切换的资源。由于存在大量相互竞争的规则和不同级别的抽象,这些开销可能开始限制整个系统的性能。但是在给出的示例中,仅在一种情况下在线切换上下文是有意义的:

await asyncio.sleep(1)

如何禁止某些异步功能的上下文切换?

2 个答案:

答案 0 :(得分:4)

首先,默认情况下,在您的示例上下文中不会切换。换句话说,在协程遇到实际受阻的东西(例如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时,事件循环才得到控制。

尽管它是默认事件循环实现的详细信息。


第二点,也许更重要的是,与使用异步处理I / O所带来的好处相比,在协程之间切换非常便宜。

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

由于协程太多而导致代码变慢的情况是不太可能的,但是即使发生这种情况,您也应该研究一下uvloop之类的更有效的事件循环实现。

答案 1 :(得分:0)

我想指出的是,如果您运行足够多的协程以至于切换上下文的开销成为问题,则可以使用Semaphore确保减少并发。最近,我将运行HTTP请求的协程的并发性从1000减少到50,从而使性能提高了约2倍。