在异步编程中,变量竞争是个老生常谈的问题。asyncio.Lock 通过协程排队确保临界区原子性,避免异步变量竞争;使用时需要全局单例、用 async with 自动管理、禁用嵌套加锁,而且不能与 threading.Lock 混用。下面从原理到实践,逐一拆解。

Python如何解决异步环境下的变量竞争_使用asyncio.Lock保证原子性

asyncio.Lock 为什么能解决变量竞争

异步任务共享变量时,await 可能打断执行流,导致多个协程在未完成读-改-写前交叉执行。比如对一个整数做 counter += 1,底层是“读值→计算→写回”三步,中间被切走就会丢更新。asyncio.Lock 不是阻塞线程,而是让协程排队——只有拿到锁的协程能进临界区,其他协程挂起等待,从而保证这段逻辑原子性。

怎么正确使用 asyncio.Lock 避免死锁和遗漏

常见错误是忘记 await lock.acquire() 或漏掉 lock.release(),更稳妥的是用 async with lock: 自动管理生命周期。注意:锁对象必须是同一个实例,不能每次 new 一个;且不能在非 async 函数里调用 await

Lock 和普通 threading.Lock 的关键区别

threading.Lock 是线程级同步原语,用在多线程中;asyncio.Lock 是协程级,只在同一个 event loop 内生效。混用会出问题:比如在 loop.run_in_executor 里用 asyncio.Lock 就无效,因为那是在新线程里跑的同步代码,得换 threading.Lock 或用 asyncio.to_thread + 线程锁配合。

实际例子:计数器并发更新不丢数据

下面这个例子演示了没锁 vs 有锁的区别。去掉 async with lock:,最终 counter 很可能小于 100;加上之后稳定输出 100。

import asyncio

counter = 0
lock = asyncio.Lock()  # 全局单例

async def increment():
    global counter
    for _ in range(10):
        async with lock:  # 关键:确保 += 原子执行
            counter += 1

async def main():
    await asyncio.gather(*[increment() for _ in range(10)])

asyncio.run(main())
print(counter)  # 输出 100

真正容易被忽略的是:锁粒度要匹配业务语义。比如你要保护的是“查库存→扣减→写日志”整个流程,那就得把这三步全包进 async with lock:,而不是只锁其中一步。不然还是会有状态不一致风险。

本文转载于:https://www.php.cn/faq/2342381.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。