Python 中的 IO 模型总结
一、IO 的分类与区别
类型 | 描述 | 举例 |
---|---|---|
阻塞 IO | IO 调用会一直等待数据就绪,期间无法做其他事 | sock.recv() (默认) |
非阻塞 IO | IO 调用立即返回,数据没准备好时返回错误/空 | sock.setblocking(False) |
IO 多路复用 | 使用 select / poll / epoll 同时监听多个 IO 通道 | select.select() |
信号驱动 IO | IO 就绪时,内核使用信号通知用户程序 | 少见 |
异步 IO(AIO) | 用户发起 IO 后立即返回,内核完成 IO 后通知用户处理数据 | asyncio / aiofiles 等 |
二、非阻塞 IO 的本质
-
调用立即返回,不管数据是否就绪。
-
用户需要反复轮询(或使用
select/epoll
)来检查数据是否可读。 -
使用场景:服务端处理高并发连接(如 socket 编程)。
python
复制编辑
sock.setblocking(False)
try:
data = sock.recv(1024)
except BlockingIOError:
# 数据没准备好,手动处理其他任务
pass
三、异步 IO 的本质
异步 IO = 非阻塞 IO + 事件循环(Event Loop)
特点:
-
使用
async/await
写法像同步代码一样简单 -
事件循环自动调度任务,不需要手动轮询
-
程序不会被
await
阻塞,而是挂起当前任务,继续执行其他任务 -
IO 一旦就绪,自动恢复挂起的协程
python
复制编辑
async def handle_client(reader, writer):
data = await reader.read(1024) # IO 等待期间可以去处理其他任务
writer.write(b"ok")
await writer.drain()
四、async 和 await 的作用
关键字 | 作用 |
---|---|
async | 声明一个协程函数,返回的是一个“协程对象” |
await | 暂停当前协程执行,等待一个异步操作完成,再继续执行 |
-
其本质是:
await
会调用目标对象的__await__()
方法 -
事件循环会将协程对象调度成任务,并在等待完成后自动唤醒
五、异步 IO 的底层流程(以 asyncio 为例)
-
程序调用
async def
定义的函数,得到协程对象 -
协程对象被
asyncio.run()
或事件循环调度为任务 -
遇到
await
时,挂起当前协程,执行其他任务 -
IO 完成后,唤醒挂起的协程,继续执行
-
所有协程完成后,程序退出
六、阻塞 VS 非阻塞 VS 异步小结
模型 | 是否阻塞主线程 | 是否自动调度 | 写法复杂度 | 并发性能 | 使用场景 |
---|---|---|---|---|---|
阻塞 IO | 是 | 否 | 简单 | 差 | 简单客户端、脚本 |
非阻塞 IO | 否 | 否(需手动轮询) | 复杂 | 好 | 高并发服务、低层 socket 编程 |
异步 IO | 否 | 是(事件循环) | 简单 | 最优 | Web 服务、爬虫、文件 IO 等 |
七、相关术语回顾
-
事件循环(Event Loop):不断检查所有挂起任务/IO 状态,并在合适时机恢复执行。
-
协程(Coroutine):可挂起、恢复的函数,比线程更轻量。
-
__await__
:被await
调用时执行的底层方法,返回一个迭代器对象,告诉事件循环“我等什么”。 -
非阻塞:不会让程序停在某一步等待,而是能继续往下运行。
-
异步:逻辑上的分离,IO 和计算不阻塞彼此。