协程中修改全局变量值会乱,因asyncio调度不可预测导致并发读写冲突;应使用contextvars隔离协程上下文,而非依赖全局变量或asyncio.Lock。

协程里改全局变量为什么值会乱
因为协程在单线程内切换执行,asyncio 调度器不会为你隔离变量作用域。同一个全局变量被多个协程并发读写,不是线程安全问题,而是“调度不可预测”问题——你根本不知道哪个协程在哪个 await 点之后接着跑,中间可能穿插其他协程的执行。
常见错误现象:global counter 在 10 个并发 fetch_data() 协程里自增,最后结果不是 10,可能是 3、7 或偶尔是 10;或者某个协程读到的是其他协程刚写一半的中间状态。
- 别用
global存请求 ID、用户上下文、临时缓存这类“本该属于单次调用”的数据 - 全局常量(如
API_BASE_URL、MAX_RETRY)没问题,只要不被修改 - 如果必须共享可变状态,得用协程安全的原语,而不是靠“我保证只一个地方写”这种假设
用 contextvars 替代全局变量存请求上下文
contextvars 是 Python 3.7+ 官方提供的协程本地存储机制,类似线程里的 threading.local(),但按协程生命周期隔离。每个协程拥有自己的一份变量副本,互不干扰。
使用场景:记录当前请求的 trace_id、当前认证的 user_id、数据库连接上下文等需要“随协程流转”的数据。
示例:
import contextvars
<p>request_id_var = contextvars.ContextVar('request_id', default=None)</p><p>async def handle_request(req_id):
token = request_id_var.set(req_id) # 绑定到当前协程
try:
await do_something()
finally:
request_id_var.reset(token) # 清理,避免泄漏</p><p>async def do_something():
req_id = request_id_var.get() # 拿到当前协程的值,不会串
print(f"Working on {req_id}")
</p>
- 必须显式
set()和reset(),漏掉reset可能导致子协程继承不该有的值 - 不能在普通函数(非协程)里用
contextvars传值,它只在async栈有效 - 变量名(如
'request_id')只是调试标识,不参与作用域控制
asyncio.Lock 不能解决全局变量竞态,只能保原子写
有人试过给全局计数器加 asyncio.Lock,发现还是不准。原因很简单:Lock 只能保证“读-改-写”三步不被中断,但无法阻止多个协程同时进入同一段逻辑、各自读到旧值再覆盖——这是逻辑层问题,不是同步层问题。
错误示范:
counter = 0
lock = asyncio.Lock()
<p>async def bad_inc():
async with lock:</p><h1>这里仍可能多个协程都读到 0,然后都写 1</h1><pre class="brush:php;toolbar:false;"> global counter
counter += 1
-
asyncio.Lock适合保护真正需要跨协程协调的资源,比如共享的文件句柄、限流计数器(需严格总数一致) - 但绝大多数“本该按请求隔离”的状态,不该放在全局,而该用
contextvars或传参 - 如果真要用锁保护全局可变状态,务必把整个读-改-写逻辑包进
async with lock:,且避免在锁内await
什么时候该把状态从全局挪到参数或类实例
判断标准很直接:这个值是不是每次调用协程时都可能不同?如果是,它就不该是模块级变量。
典型该挪走的情况:
- HTTP 请求头、查询参数、body 解析结果 → 作为参数传进协程,或封装进
Request实例 - 数据库连接池 → 用
asyncpg.create_pool()创建一次,作为依赖注入进服务类,而不是存在global pool - 缓存对象(如
aiocache.Cache)→ 初始化后传入协程或作为类属性,避免多协程共用同一 cache 实例导致 key 冲突或过期混乱
容易被忽略的一点:第三方库的异步客户端(如 aiohttp.ClientSession)本身不是线程安全,更不是协程安全——它不保存请求上下文,但如果你把它设为全局并复用,多个协程并发调用 session.get() 是允许的;可一旦你在 session 上挂了自定义字段(比如 session.current_user = ...),就立刻变成协程间污染。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











