🐍Python 并发与异步

contextvars 与异步上下文

面试回答

常见问法

  • threading.local 在 asyncio 里为什么会出问题?
  • contextvars 是什么?和线程局部变量有什么区别?
  • asyncio 任务之间的上下文是怎么传递的?
  • 怎么在异步代码里安全传递 trace_id / user_id?

回答

待补充:核心是「线程局部在协程里被共享,所以需要 PEP 567 的 contextvars」,用一个 trace_id 注入的典型场景讲清楚。

追问

  • ContextVar.set() 返回的 Token 是什么?
  • asyncio.create_taskensure_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,导致请求间串扰
  • setreset,长生命周期任务里污染
  • 通过 run_in_executor 调用同步代码,忘了 Context 不会自动传(要 contextvars.copy_context().run(...))
  • 以为全局变量配 lock 就能替代 contextvars

记忆技巧

  • 口诀:线程用 threading.local,协程用 ContextVar
  • 复制点:新 Task 会复制,executor 不复制
  • 清理口诀:set 要配 reset,或者用 try/finally