🐍Python 并发与异步
contextvars 与异步上下文
面试回答
常见问法
threading.local在 asyncio 里为什么会出问题?contextvars是什么?和线程局部变量有什么区别?- asyncio 任务之间的上下文是怎么传递的?
- 怎么在异步代码里安全传递 trace_id / user_id?
回答
待补充:核心是「线程局部在协程里被共享,所以需要 PEP 567 的 contextvars」,用一个 trace_id 注入的典型场景讲清楚。
追问
ContextVar.set()返回的 Token 是什么?asyncio.create_task和ensure_future的上下文行为一致吗?- 在 ThreadPoolExecutor 里 ContextVar 会不会自动传递?
- FastAPI 的请求上下文是怎么实现的?
原理展开
threading.local:每个线程独立,但 asyncio 一个线程里跑多协程ContextVar:每个协程/任务独立,跨 await 保持- Context 复制:
asyncio.create_task会复制当前 Context - Token 模型:
token = var.set(value); var.reset(token)精确恢复 copy_context():手动传播到线程池 / 子协程- 典型应用:分布式追踪、日志上下文、事务范围、i18n locale
易错点
- 在异步场景里用
threading.local,导致请求间串扰 - 只
set不reset,长生命周期任务里污染 - 通过
run_in_executor调用同步代码,忘了 Context 不会自动传(要contextvars.copy_context().run(...)) - 以为全局变量配 lock 就能替代 contextvars
记忆技巧
- 口诀:线程用
threading.local,协程用ContextVar - 复制点:新 Task 会复制,executor 不复制
- 清理口诀:
set要配reset,或者用 try/finally