核心问题是多个协程或线程并发读写共享变量时缺乏同步控制;需用 asyncio.Lock 保护临界区、采用不可变更新+原子替换、或通过请求 ID 校验丢弃过期响应。

核心问题不在“await”本身,而在于多个协程或线程同时读写同一变量时缺乏同步控制。所谓“未正确加锁 await 变量”,本质是混淆了异步等待与状态保护——await 只负责暂停协程,不提供任何并发安全保证。
明确共享变量的修改边界
当循环中反复调用异步操作(如 fetch、db.query)并更新同一个变量(如 result、cache、counter),必须识别哪些操作属于临界区:即读取→计算→写入这一完整流程不可被中断。例如:
- 用户连续点击“加载更多”,触发多个
fetchItems(offset)请求,结果统一 push 到items = [] - 多个定时任务并发调用
updateConfig(),都读取旧配置、合并新字段、再写回全局 config 对象
这类场景下,变量不是“被 await 修饰”,而是“被多个并发路径共同修改”,必须显式隔离。
用协程锁(asyncio.Lock)保护临界区
在 Python asyncio 中,asyncio.Lock 是专为协程设计的同步原语,能挂起而非阻塞其他协程:
- 创建锁对象一次:
lock = asyncio.Lock() - 所有修改共享变量的协程,必须用
async with lock:包裹写操作段 - 避免在锁内做耗时 I/O(如长 sleep 或未设 timeout 的请求),否则会拖慢整体并发吞吐
示例:防止多次刷新覆盖用户数据
async def refresh_user_data(lock, user_id):async with lock:
data = await fetch_user(user_id)
global_user_cache[user_id] = data # 唯一写入点
改用不可变更新 + 原子替换
比加锁更轻量的思路:不直接修改变量,而是生成新值,再用原子操作替换整个引用。适用于字典、列表等可替换结构:
- 用
dict | new_dict(Python 3.9+)或{**old, **new}构造新配置 - 列表追加改用
items = items + fetched(注意性能,大数据量慎用) - 结合
threading.local()(多线程)或contextvars.ContextVar(协程)隔离各执行流的数据副本
优势是无锁、无等待,但需确保替换操作本身是原子的(如对全局变量赋值在 Python 中是原子操作)。
校验请求有效性,丢弃过期响应
对于 UI 触发类操作(如快速连点),可在发起请求时打上唯一标识(如时间戳、序列号),响应返回时比对是否仍为最新请求:
- 维护一个
latest_request_id全局变量 - 每次发起前更新 ID,并传入请求上下文
- 响应处理时先检查
if response.id != latest_request_id: return
这样即使并发请求都完成,也只让最后发出的那个生效,避免“后发先至”导致的状态错乱。

















