ContextVar 笔记
为什么会有ContextVar
多线程环境中,共享变量会导致线程安全问题,python 为了解决这个问题,为线程提供了local 这个对象,他是每个线程私有,在多线程环境下,每个线程的local 中存放的变量是相互隔离的,彼此不影响。
而协程是运行在线程中的,一个线程可以运行多个协程。如果是用local 就会出现多协程共享变量,这里的情况就跟对线程一样了,为了解决协程之间共享变量,于是就有了ContextVar。
ContextVar 的方法
通过源码查看类结构发现ContextVar 提供了三个方法分别是:
下面用表格记录一下3个方法的用途
方法 | 作用 | 使用场景 |
---|---|---|
ContextVar.get() | 获取当前上下文的值 | 读取当前环境配置 |
ContextVar.set() | 设置新值并返回Token | 进入新上下文环境 |
ContextVar.reset() | 恢复到指定Token之前的状态 | 退出子上下文环境 |
下面通过代码演示:
import threading
import asyncio
import contextvars
local_data = threading.local()
ctx_var = contextvars.ContextVar("ctx_data")
async def coro_task(name):
local_data.value = name
token = ctx_var.set(name)
try:
await asyncio.sleep(0.1)
print(f"协程{name}获取local_data值: {local_data.value}")
print(f"协程{name}获取ctx_var值: {ctx_var.get()}")
print("=="*10)
finally:
ctx_var.reset(token)
async def main():
token = ctx_var.set("main")
try:
await asyncio.gather(
coro_task("A"),
coro_task("B")
)
print(f"协程main获取ctx_var值: {ctx_var.get()}")
finally:
ctx_var.reset(token)
asyncio.run(main())
输出:
协程A获取local_data值: B
协程A获取ctx_var值: A
====================
协程B获取local_data值: B
协程B获取ctx_var值: B
====================
协程main获取ctx_var值: main
通过输出日志可以发现,使用local 出现了共享问题。 A 原本是想获取local中自己设置的A 的。结果因为阻塞等待,让B 将local中的值修改为B.。而当A 在唤醒的时候,读到的是B 而不是自己设置的A了。而使用ContextVar就不会出现共享问题。
而 ctx_var.reset(token) 是退出token 对应的设置,
async def main():
token = ctx_var.set("main1")
try:
print(f"第一次获取ctx_var值: {ctx_var.get()}") #第一次获取ctx_var值: main1
token2 = ctx_var.set("main2")
print(f"第二次获取ctx_var值: {ctx_var.get()}") # 第二次获取ctx_var值: main2
token3 = ctx_var.set("main3")
print(f"第三次获取ctx_var值: {ctx_var.get()}") # 第三次获取ctx_var值: main3
ctx_var.reset(token2) # 退出 回到上一次设置的值
print(f"第四次获取ctx_var值: {ctx_var.get()}") # 第三次获取ctx_var值: main1
finally:
ctx_var.reset(token)
asyncio.run(main())
通过代码可以看出。使用tx_var.reset(token) 可以将tx_var回滚到指定token 生成前的值。
注意:ContextVar 不会像local一样自动释放,而是需要手动执行reset方法