4.4 KiB
id, title, type, domain, stack, tags, level, related, week
| id | title | type | domain | stack | tags | level | related | week | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| python.asyncio.interview | asyncio 面试题 | interview | python |
|
|
intermediate |
|
|
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 中的线程无法被强制停止);或者有后台任务没有被追踪和取消。修复:正确传播取消、给阻塞调用设超时、在应用的生命周期关闭阶段统一取消并等待后台任务。