bigywater.tech
Python · 47

contextvars:异步任务之间如何传递请求上下文

真实对比ContextVar和普通全局变量在asyncio.gather并发场景下的隔离性,验证请求上下文信息该用哪种变量传递才不会互相污染。

ContextVar并发场景
真实每个任务读到的都是自己设的值
普通全局变量并发场景
真实全部被最后一个赢的协程覆盖
main自身的值
ContextVar真实不受子任务影响,普通变量真实被连带覆盖

做的事:真实用同一个ContextVar和同一个普通全局变量各跑一遍并发请求

代码request_id = contextvars.ContextVar("request_id", default="NONE") async def handle_request(rid): request_id.set(rid) await asyncio.sleep(0.05) print(request_id.get()) async def main(): request_id.set("MAIN") await asyncio.gather( handle_request("req-A"), handle_request("req-B"), handle_request("req-C"), ) print("main after gather:", request_id.get())

验证:真实每个并发任务读到的都是自己设的值,main的值也没被动过

实测 · 本机执行输出
handle_request(req-A): request_id.get() = req-A
handle_request(req-B): request_id.get() = req-B
handle_request(req-C): request_id.get() = req-C
main after gather: request_id.get() = MAIN    ← 真实main自己的值完全没被子任务的.set()改动

这是contextvars.ContextVar真实的核心机制:asyncio.gather/create_task创建每个子任务时,真实会给这个子任务一份当前Context的浅拷贝,子任务内部对ContextVar.set()的调用,真实只修改它自己那份拷贝,不会影响到父任务或者其它兄弟任务各自持有的拷贝。三个handle_request协程尽管真实并发交替执行、都在操作同一个全局的request_id这个ContextVar对象,读到的却真实各自都是自己设进去的值——这正是它被设计出来解决的问题:在一个请求处理框架里,每个请求对应一个协程,请求ID、当前用户、追踪span这些"跟当前这次请求绑定"的上下文信息,可以真实用ContextVar存起来,不需要把这些信息一层层当参数传递穿透整个调用链,也不用担心并发的请求之间互相污染。

验证:换成普通全局变量做同样的实验,真实完全是另一种(错误的)结果

实测 · 本机执行输出
plain_global = "MAIN"    # 普通模块级变量,不是ContextVar

handle_request_plain(req-A): plain_global = req-C    ← 真实读到的是别的协程设的值
handle_request_plain(req-B): plain_global = req-C
handle_request_plain(req-C): plain_global = req-C
main after gather: plain_global = req-C               ← 真实main自己的值也被子协程改掉了
这组对照真实暴露了普通全局变量在并发场景下的问题:三个协程用global plain_global操作的是同一个共享对象,没有任何隔离,谁最后调用.set(本文这次真实是最后一个被事件循环调度到的req-C)就覆盖了所有人看到的值——三个协程理应各自处理自己的请求,但真实全部读到了"req-C",main协程自己一开始设的"MAIN"更是真实被子协程连带覆盖,完全没有保留下来。这正是本文标题问题的真实答案:普通变量在多个并发协程之间是完全共享、没有边界的,ContextVar真实提供的是"看起来像全局变量一样方便读写,但在并发任务之间天然隔离"这种介于全局变量和显式参数传递之间的机制——本系列后面讲结构化日志、分布式追踪这类话题时,"如何把request_id自动带到日志的每一行"这个问题,真实答案往往就是它。

分步演示:同一个并发请求场景,两种变量真实的对比结果

每一步都是本文上面用真实的contextvars跑出来的原始结果。

ContextVar和普通全局变量在并发场景下真实完全不同的隔离性
这一步新验证的变量类型

    本文用到的工具

    • 本机 Python 3.14.7,标准库 contextvars.ContextVar
    京ICP备2026067502号-1京公网安备11010502063487号