asyncio 的事件循环(event loop)是如何运行的?
你可以把 event loop 想象成“一个永远在值班的调度员”。它的活儿其实很简单:不断盯着一堆任务(协程、回调、IO操作),看哪个准备好了,就立刻让它上 CPU 跑一会儿。然后再换下一个,就这么循环往复。
跟平时我们写的 for 循环不同,这玩意是“不会停”的。只要你没手动关掉,它就一直在跑,像个服务器进程一样。
启动的时候发生了啥?
最常见的用法大家都写过:
import asyncioasyncdefsay_hello():
print("hello ...")
await asyncio.sleep(1)
print("world!")
asyncio.run(say_hello())
这里的 asyncio.run() 干了几件事:
创建一个新的事件循环(loop)。 把你传进去的那个协程任务塞到 loop 里。 启动 loop,不停轮询,看哪个协程 ready 了就执行。 等到任务都结束了,再把 loop 关掉,顺便清理下资源。
看起来就一句话,背后其实就是 loop 在死循环。大概逻辑类似这样(伪代码):
whileTrue:
ready_tasks = get_all_ready_tasks()
for task in ready_tasks:
run_task(task)
if no_more_tasks():
break
那些 await 背后发生的事
比如你写了 await asyncio.sleep(1),这时候 Python 并不是卡住一整秒,而是告诉事件循环: “哥们我先歇一秒,这期间你可以去跑别的任务,等一秒过去再叫我”。
于是 loop 就把这个协程挂起来,放进一个定时器队列里。等一秒时间到了,loop 再把它捞出来,继续执行后半段。
这就是所谓的“协作式切换”:谁主动 await,谁让出 CPU。
有点像 IO 多路复用
事件循环的核心就是不断等待 IO,就像 select/poll/epoll 那一套。loop 里会有个系统调用,等着 socket、管道、文件描述符啥的有事件发生,一旦触发了,loop 马上调度相关的协程去处理。
所以 asyncio 本质就是封装了一个跨平台的 IO 复用器 + 调度器。你写的协程代码,最后都会变成 loop 里的任务对象。
比如你要同时发起两个网络请求,如果是同步写法就是一个等一个,但在 asyncio 里,可以一起跑:
import asyncioasyncdeffetch(name, delay):
print(f"{name} start")
await asyncio.sleep(delay)
print(f"{name} done after {delay}s")
asyncdefmain():
tasks = [
asyncio.create_task(fetch("task1", 2)),
asyncio.create_task(fetch("task2", 1)),
]
await asyncio.gather(*tasks)
asyncio.run(main())
执行的时候,task1 和 task2 都会被 loop 调度。因为 task2 只需要 1 秒,它会先完成,然后 task1 再结束。整个流程 loop 全程盯着:谁准备好了就调度谁。
事件循环 = 死循环 + 调度器,本质是个任务分配器。 await是关键,它告诉 loop:我这边还没 ready,你先去忙别的。背后靠的是 IO 多路复用,才能做到“一核多用”。 写异步代码时,真正的主角其实是 loop,我们写的协程只是被它管控的工人。
-END-
我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html
虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》,总量高达650GB,点击下方公众号回复关键字 python 全部免费领