事件循环是为调度和响应异步事件而生的核心机制;它通过i/o多路复用监听系统事件,将就绪的协程放入队列,并在await处精确移交控制权,实现协作式抢占调度。

事件循环与异步事件的协同,不是“配合工作”的关系,而是**事件循环就是为调度和响应异步事件而生的核心机制**。它本身不产生事件,但负责监听、排队、分发和驱动所有异步事件的处理流程。
事件循环如何响应异步事件
异步事件(如网络数据到达、文件读取就绪、定时器到期)由操作系统内核通过 I/O 多路复用(epoll/kqueue/select)通知事件循环。事件循环收到通知后,并不立即执行业务逻辑,而是把关联的回调或等待中的协程重新标记为“可运行”,放入就绪队列。
- 例如:一个 await asyncio.open_connection() 挂起后,事件循环会注册该 socket 的“可读”事件;当远端发来 SYN 或数据包抵达网卡缓冲区,内核触发事件,事件循环便唤醒对应协程
- 再如:await asyncio.sleep(1) 实际是向事件循环注册一个 1 秒后的定时事件;时间一到,事件循环就把该协程放回就绪队列
协程是事件的“消费者”,事件循环是“调度员”
协程本身不直接监听系统事件,它只通过 await 表达式声明“我需要等某件事发生”。事件循环根据 await 后的对象类型(Future、Task、Stream 等),自动完成事件注册、状态跟踪与恢复调度。
- await 一个 HTTP 请求时,底层 aiohttp 会把 socket 加入事件循环监控列表
- await 一个 asyncio.Queue.get(),事件循环会在队列非空时触发协程继续
- 没有 await,协程只是普通函数对象;只有被事件循环驱动并参与事件等待,才真正进入异步生命周期
协同的关键不在代码里,而在调度时机
真正的协同体现在控制权移交的精确性上:协程在 await 处暂停,事件循环立刻检查其他就绪任务;某个异步事件就绪后,事件循环不等当前协程自然结束,而是强制切换上下文,让等待它的协程恢复执行。
- 这种协作式抢占(cooperative preemption)避免了线程锁和上下文切换开销
- 但它也要求所有 awaitable 对象必须与事件循环兼容——不能在协程中调用 time.sleep(),也不能在事件循环线程里执行 CPU 密集型同步计算
不是所有异步事件都走同一条路径
事件循环统一管理,但异步事件来源不同,注册方式也有差异:
- I/O 事件:通过 add_reader/add_writer 注册文件描述符,或由高层 API(如 asyncio.open_connection)自动完成
- 定时事件:通过 call_later/call_at 插入最小堆,按时间顺序触发
- 外部信号:如 asyncio.add_signal_handler 可将 SIGINT 等映射为协程可 await 的 Future
- 程序主动通知:asyncio.create_future().set_result() 也能触发一次协程恢复











