本文主要是介绍为什么会有协程安全问题?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
更多:并发异步编程之争:协程(asyncio)到底需不需要加锁?
1、案例
因为所有的协程都是在同一个线程里,所以即使最开始在await前拿到的counter=0,但只要同一线程里counter发生了变化,切换到新协程时,它拿的就是counter的最新值。
import asyncio
import time# 一个简单的协程,模拟对共享数据的修改
async def increment(shared_data):task = asyncio.current_task() # 获取当前正在执行的 Task 对象print(f"当前运行的Task name: {task.get_name()}")print(f"传来的shared_data={shared_data}")for _ in range(100):await asyncio.sleep(2) # time.sleep(2) task1 = asyncio.current_task() # 获取当前正在执行的 Task 对象print(f"等待结束,Task name: {task1.get_name()}")shared_data['counter'] += 1print(f"累加后shared_data={shared_data}")async def main():# 创建一个字典来存储共享数据shared_data = {'counter': 0}# 创建多个任务来修改共享数据tasks = [asyncio.create_task(increment(shared_data)) for _ in range(5)]# 等待所有任务完成await asyncio.gather(*tasks)# 打印最终的计数结果print(f"Expected counter value: 500")print(f"Actual counter value: {shared_data['counter']}")# 运行事件循环
asyncio.run(main())
模拟同步操作:time.sleep(2)
同步阻塞,不会出现协程的让权了,但同一时刻仍然只有一个协程在执行,发现下一个协程必须等上一次协程全部跑完,所以输出是:
task1:counter从0递增到100
task2:counter从100递增到200
task3:counter从200递增到300
…
最终结果是500。
模拟异步操作: await asyncio.sleep(2)
发现最终的结果是500,但是中间状态下,每个task真正执行“shared_data[‘counter’] += 1”这行代码时的counter值并不都是从0开始!!!
输出结果举例:
task1 抢到执行权,counter = 0+1=1
task3 抢到执行权,counter = 1+1=2
task2 抢到执行权,counter = 2+1=3
task4 抢到执行权,counter = 3+1=4
task5 抢到执行权,counter = 4+1=5
task3 抢到执行权,counter = 5+1=6
…
模拟异步下加锁
发现异步加锁后,和同步阻塞的效果是一样的…
import asyncio
import time# 一个简单的协程,模拟对共享数据的修改
async def increment(shared_data):task = asyncio.current_task() # 获取当前正在执行的 Task 对象print(f"当前运行的Task name: {task.get_name()}")print(f"传来的shared_data={shared_data}")async with lock:for _ in range(100):await asyncio.sleep(2) task1 = asyncio.current_task() # 获取当前正在执行的 Task 对象print(f"等待结束,Task name: {task1.get_name()}")shared_data['counter'] += 1print(f"累加后shared_data={shared_data}")async def main():# 创建一个字典来存储共享数据shared_data = {'counter': 0}# 创建多个任务来修改共享数据tasks = [asyncio.create_task(increment(shared_data)) for _ in range(5)]# 等待所有任务完成await asyncio.gather(*tasks)# 打印最终的计数结果print(f"Expected counter value: 500")print(f"Actual counter value: {shared_data['counter']}")lock = asyncio.Lock()
# 运行事件循环
asyncio.run(main())
结论
- 如果只关注最终的“500”是否正确,那协程在异步阻塞下是安全的;
- 如果关注不管启动服务多少次,
taski
执行第j
次时拿到的counter值,并根据这个中间状态
做其他判断,那就是不安全的!!!
比如,本例中,在模拟异步操作时,task3第一次抢到执行权得到counter=2,等到第二次抢到执行权得到counter=6。
如果重新运行当前xx.py文件,那task3每次抢到执行权时的counter值都可能是不一样的!
这篇关于为什么会有协程安全问题?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!