FAQ
asyncio 会让 Python 代码多核并行吗
默认不会。asyncio 主要解决 I/O 等待重叠问题。CPU 密集计算需要多进程、扩展模块释放 GIL、专门计算框架,或把阻塞函数放进 executor。
async def 里面能不能调用同步函数
能调用,但如果同步函数耗时或阻塞,就会堵住事件循环。短小纯计算可以接受;网络请求、数据库访问、文件批处理这类阻塞调用要换异步库,或用 asyncio.to_thread() 临时隔离。
为什么我写了 async 还是串行
最常见的原因是你在循环里连续 await:
for url in urls:
await fetch(url)每次都等完才进入下一次。独立任务应该先创建,再统一等待:
await asyncio.gather(*(fetch(url) for url in urls))如果数量很大,要加限流。最直接的写法是 Semaphore:
sem = asyncio.Semaphore(10)
async def fetch_limited(url):
async with sem:
return await fetch(url)
results = await asyncio.gather(*(fetch_limited(url) for url in urls))如果任务是一边产生一边处理,改用 Queue 和固定数量的 worker。
gather 和 TaskGroup 选哪个
简单收集一组独立结果,用 gather。一组任务属于同一个业务作用域,任一个失败就应该取消整组,用 TaskGroup。
为什么 Task 取消后 finally 还会执行
取消是向协程注入 CancelledError,不是强制杀掉执行流。异常会正常展开调用栈,所以 finally 有机会清理资源。
为什么不应该吞掉 CancelledError
上层的 timeout、TaskGroup 和关闭流程都依赖取消传播。吞掉它会让上层误判任务状态。清理后通常应该重新 raise。
为什么 writer.write 后还要 drain
write() 可能只是写入内部缓冲。drain() 是流控等待点,缓冲过大时暂停当前 Task,避免内存持续增长。
Queue.task_done 有什么用
queue.join() 等的是“所有 put 进去的 item 都处理完”。每个消费者处理完一个 item 后必须调用 task_done(),否则 join() 不会返回。
get_running_loop 和 get_event_loop 有什么区别
在 coroutine 或 callback 里优先用 get_running_loop(),它只返回当前正在运行的 loop。get_event_loop() 带有历史兼容行为,和当前线程、policy、版本有关。
为什么教程还要手写 mini_asyncio
因为很多 asyncio 概念只有跑过一个最小实现才会变清楚。mini_asyncio 只把 Future、Task、timer、selector、Queue 这几段放在一份短代码里,方便你对照标准库源码继续读。