不死心,再来问一遍关于 Python 的 asyncio 问题

2019-07-22 17:42:42 +08:00
 waibunleung

有两段代码,是关于 asyncio 的。
代码段一:

import asyncio

async def worker_1():
    print('worker_1 start')
    await asyncio.sleep(1)
    print('worker_1 done')

async def worker_2():
    print('worker_2 start')
    await asyncio.sleep(2)
    print('worker_2 done')

async def main():
    print('before await')
    await worker_1()
    print('awaited worker_1')
    await worker_2()
    print('awaited worker_2')

%time asyncio.run(main())

########## 输出 ##########

before await
worker_1 start
worker_1 done
awaited worker_1
worker_2 start
worker_2 done
awaited worker_2
Wall time: 3 s

代码段二:

import asyncio

async def worker_1():
    print('worker_1 start')
    await asyncio.sleep(1)
    print('worker_1 done')

async def worker_2():
    print('worker_2 start')
    await asyncio.sleep(2)
    print('worker_2 done')

async def main():
    task1 = asyncio.create_task(worker_1())
    task2 = asyncio.create_task(worker_2())
    print('before await')
    await task1
    print('awaited worker_1')
    await task2
    print('awaited worker_2')

%time asyncio.run(main())

########## 输出 ##########

before await
worker_1 start
worker_2 start
worker_1 done
awaited worker_1
worker_2 done
awaited worker_2
Wall time: 2.01 s

问题:代码段一里面的协程(coroutine)换成代码段二的任务(task)后,为什么执行顺序就变了?这个过程中发生了什么事情?

说说我的猜想:
发现调用 asyncio.run(main()) 或者 [asyncio.gather()->asyncio.get_event_loop()->loop.run_until_complete()]都会将一个 coroutine 转化成 task/future 再放 event loop 里面去, 交由 event loop 去管理这些 task/future。代码段一只将 main()这个 coroutine 封装成了 task 加入到 event loop 中,所以整个 event loop 中只有一个 task 在走,在这个 task 中代码是顺序执行的,所以最后呈现出同步执行的结果;
但是代码段二调用了两次 asyncio.create_task(),这个方法会将一个 coroutine 转换成一个 task 并且放到 event loop 中,所以整个 event loop 其实有三个 task ( main,task1,task2 ),之后程序就交给 event loop 来调度,执行顺序就变不同了。 这个假设目前来看好像能解释得通

最后,希望各位能指点一下~

6496 次点击
所在节点    Python
90 条回复
zzth370
2019-07-23 22:52:56 +08:00
而你代码二中 await task 造成了一定的误解,造成主动 await 才会执行 task 的假象
个人见解
waibunleung
2019-07-23 22:58:32 +08:00
@zzth370 你只改了 main 没有改 worker 里面的?
zzth370
2019-07-23 23:04:29 +08:00
import asyncio


async def worker_1():
print('worker_1 start')
await asyncio.sleep(1)
print('worker_1 done')

async def worker_2():
print('worker_2 start')
await asyncio.sleep(2)
print('worker_2 done')

async def main():
task1 = asyncio.create_task(worker_1())
task2 = asyncio.create_task(worker_2())
print('before await')
await asyncio.sleep(2)
print('awaited worker_1')
await asyncio.sleep(1)
print('awaited worker_2')


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

源码是这样的,就修改了下 main()里面的
wwqgtxx
2019-07-23 23:54:25 +08:00
@zzth370 其实可以用一个比较简单的方式解释,await 导致了 task 切换,但是 create_task 之后这个 task 已经放进等待列表,所以在 main task 中 await 任何 future 都会导致 worker 被执行,至于 worker_1 和 worker_2 的执行循序,这个就要看 EventLoop 的具体实现了
waibunleung
2019-07-23 23:55:35 +08:00
@zzth370 明白你的意思了,其实只要一个协程里面 await 了,就会进行切换,至于切换到哪一个就看 event loop 的调度了
waibunleung
2019-07-23 23:57:09 +08:00
@wwqgtxx 啊相差了一分钟,其实想表达的是同样的意思,我等下整理一篇博客出来你可以帮忙看看吗
wwqgtxx
2019-07-24 00:35:52 +08:00
@waibunleung 如果你仔细看过 EventLoop 的实现代码,你会发现其实 asyncio 内部很喜欢用在一个函数结尾调用 call_soon 的方式实现循环,比如
def _run(a,loop):
____if a == XXX:
________干一些事
____else:
________干另一些事
____loop.call_soon(a,loop)
____return
这样实现的代码虽然看起来复杂,可能能让你更明白事件循环的执行过程
wwqgtxx
2019-07-24 00:39:50 +08:00
@wwqgtxx 修正一下,是
____loop.call_soon(_run,a,loop)
这里会发现 call_soon 传入的参数是个普通函数而不是一个 coroutine,为什么这么设计就很有意思了
waibunleung
2019-07-24 01:25:12 +08:00
@wwqgtxx
文章写好啦 https://blog.csdn.net/qq_29785317/article/details/97054589
大家看看有什么需要修改的地方~~多多指正噢
waibunleung
2019-07-24 01:29:06 +08:00
@wwqgtxx 我看到了 _run_once 这一部分,不知道是不是你说的 EventLoop 的实现,我觉得是其中一部分

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/585164

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX