Files
zhuangang/topics/python/asyncio/interview.md
T

4.4 KiB
Raw Blame History

id, title, type, domain, stack, tags, level, related, week
id title type domain stack tags level related week
python.asyncio.interview asyncio 面试题 interview python
asyncio
asyncio
协程
并发
面试
intermediate
python.asyncio.coroutines-tasks
python.asyncio.concurrency-control
4
16

asyncio 面试题

基础

【基础】协程、线程、进程有什么区别?

参考答案:进程有独立的内存空间,切换和通信开销大,能利用多核;线程共享进程内存,由操作系统抢占式调度,在 CPython 中受 GIL 限制不能并行执行 Python 代码;协程是用户态的、协作式调度的执行单元,在 await 处主动让出控制权,切换开销极小,单线程即可同时处理大量 IO 等待。

【基础】asyncio 适合什么场景?不适合什么场景?

参考答案:适合 IO 密集、并发连接多的场景:调用大模型 API、HTTP 请求、数据库查询、WebSocket / SSE 长连接。不适合 CPU 密集计算(单线程,无法加速),这类任务用多进程或交给 C 扩展库。

【基础】调用 async def 函数但没有 await 会怎样?

参考答案:只会创建协程对象,函数体不会执行,Python 会给出 “coroutine was never awaited” 警告。这一点和 JS 不同——JS 的 async 函数调用后立即开始执行。

【基础】为什么在协程里不能使用 time.sleep 或 requests?

参考答案:事件循环是单线程的,阻塞调用会让整个线程停住,所有其他协程都无法运行,服务的并发能力退化为 1。应该用 asyncio.sleep、httpx.AsyncClient 等异步库;只能用同步库时用 asyncio.to_thread 放到线程池执行。

进阶

【进阶】asyncio.gather 和 TaskGroup 有什么区别?

参考答案:gather 在某个任务抛异常时立即把异常抛给调用方,但其余任务继续运行;加 return_exceptions=True 则把异常作为结果返回。TaskGroup(3.11+)是结构化并发:一个任务失败时取消组内其他任务,等它们全部结束后,以 ExceptionGroup 抛出所有异常,不会留下“孤儿任务”。

【进阶】怎么限制并发数?

参考答案:用 asyncio.Semaphore(n),在每个任务的实际调用外层 async with sem:;或者用固定数量的 worker 从 asyncio.Queue 取任务。注意多个请求要共享同一个 Semaphore;多进程 / 多实例时需要 Redis 等全局限流。

【进阶】asyncio 的取消机制是怎样的?

参考答案:task.cancel() 请求取消,任务在下一个 await 点收到 CancelledError。任务可以捕获它做清理(关闭连接、记录状态),但应该重新抛出,否则取消失效。asyncio.timeout / wait_for 超时也是通过取消内部任务实现的。CancelledError 继承自 BaseException,所以 except Exception 不会误吞它。

场景题

【场景题】需要对 1 万条文本调用大模型生成摘要,服务商限制每分钟 600 次请求,怎么设计?

参考答案:用 Semaphore 或 worker 池限制并发,再加速率控制(令牌桶,每秒约 10 次);每次调用设超时;对 429、超时、5xx 做带抖动的指数退避重试,429 响应如果带 Retry-After 就按它等待;单条失败记录下来,最后汇总重跑;记录进度(已完成的 ID)以便中断后续跑;结果写入数据库而不是全放内存。规模更大时用消息队列分发给多个 worker。

【场景题】FastAPI 接口里调用了一个同步的 SDK,发现服务在压测时吞吐很低,为什么?怎么改?

参考答案:在 async def 路由里直接调用同步 SDK,阻塞了事件循环,所有请求排队。改法:换成 SDK 的异步版本;或者 await asyncio.to_thread(sdk.call, ...);或者把路由定义为普通 def,FastAPI 会把它放到线程池执行。然后重新压测确认。

【场景题】一个异步任务在服务关闭时总是卡住,可能是什么原因?

参考答案:任务里捕获了 CancelledError 却没有重新抛出(或者用了裸 except:),导致取消无效;或者任务在执行不响应取消的阻塞调用(同步 IO、to_thread 中的线程无法被强制停止);或者有后台任务没有被追踪和取消。修复:正确传播取消、给阻塞调用设超时、在应用的生命周期关闭阶段统一取消并等待后台任务。