【问题标题】:How can I prevent context switching when calling an async function?调用异步函数时如何防止上下文切换?
【发布时间】:2019-02-25 05:57:46
【问题描述】:

如果我使用异步函数,那么堆栈上面的所有函数也应该是异步的,并且它们的调用前面应该有 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)

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

【问题讨论】:

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


【解决方案1】:

首先,在您的示例上下文中,默认情况下不会切换。换句话说,直到协程遇到实际阻塞的事情(比如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 相比,在协程之间切换非常便宜。

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

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

【讨论】:

【解决方案2】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-18
    • 1970-01-01
    • 2021-01-15
    • 2016-09-10
    • 1970-01-01
    相关资源
    最近更新 更多