asyncio是Python深度题,区分"会写async def"和"懂底层发生什么"。后端、数据、ML serving这些IO密集场景必考。
先讲模型:asyncio是单线程协作式并发。一个事件循环每次只跑一个协程;协程在await处主动让出,loop再调度下一个。
协程(coroutine):async def函数调用但没await时得到的对象。它是挂起的代码——保留局部状态但没在跑。await它或包成Task才会执行。
Future:底层占位符——"这里将来会有个值"。状态pending/finished/cancelled。业务代码几乎不直接创建Future,底层库在用。
Task:Future的子类,包装一个协程并注册到事件循环去跑。用asyncio.create_task()或asyncio.gather()隐式创建。多个Task在同一线程上并发。
循环机制:每个await要么awaited的值已就绪立即返回,要么告诉loop"FD/定时器/Future就绪时叫我"。loop在epoll里睡觉,事件触发就恢复对应协程。
asyncio是Python自带的单线程协作式并发——一个线程、一个事件循环、多个协程轮流执行。协程、Task、Future三个概念最容易搞混。协程是async def函数不加await调用时得到的对象:async def foo(): ...,c = foo()返回的是coroutine对象不是结果。它是挂起的代码——保留了局部状态但还没执行。让它跑有两种方式:另一个协程里await它(串行),或者包成Task(并发)。Future是底层占位符——"这里将来会有个值"。它有状态pending/finished/cancelled,和一个最终被set的result。业务代码几乎不直接创建Future,底层库在用。Task是Future的子类,它包装一个协程并注册到事件循环,让这个协程和其他Task并发跑。asyncio.create_task(coro)显式创建,asyncio.gather(*coros)隐式批量创建——gather会等所有task完成返回result列表。事件循环本身是个调度器+selector。每个await点要么awaited的值已就绪立即返回,要么告诉loop"文件描述符/定时器/Future就绪了叫我"。loop在epoll(Linux)里睡觉直到有注册事件触发,再恢复对应协程。因为全程单线程,协程之间没有GIL竞争——就是你一步我一步轮流。这让asyncio非常适合IO密集——同时跑几千个HTTP请求或数据库查询毫不费力——但完全不适合CPU密集,CPU活会卡死这个单线程。最经典的坑是协程里调了阻塞函数(requests.get、time.sleep、同步DB驱动)——整个loop冻住,所有在等的协程都被拖住。CPU任务要用asyncio.to_thread或run_in_executor把阻塞调用丢到线程池。
协程/Task/Future三者的区别是面试官想听的,要明确分开讲。
必须点出"asyncio单线程只解决IO并发",混淆threading是红线。
能讲"阻塞调用卡死loop"这个实战坑是加分项。
忘了to_thread/run_in_executor这些API名,即答侠可以实时提示。
多IO并发场景是——没有线程切换开销,单协程内存占用极小。CPU场景两个都救不了,要用multiprocessing。
可以但要小心。同步→异步:asyncio.run(coro())。异步→同步:await asyncio.to_thread(blocking_fn)避免卡loop。
gather收集结果且默认首个异常就抛;wait更灵活——可以等FIRST_COMPLETED或FIRST_EXCEPTION。常规用gather。