我很难弄清楚为什么不注释await asyncio.sleep(1)
会使Test
被打印10次。使用异步时,似乎val
属性的初始化失败。
不应遵循初始化,并且只能打印一次,因为它是同一实例。出现awaitable
个呼叫时如何解决此问题?
class TestAsync:
def __init__(self):
self.val = None
async def some_fun(self):
if not self.val:
# await asyncio.sleep(1) # Magic line
print('Test')
self.val = 10
async def main(loop):
a = TestAsync()
tasks = [a.some_fun() for _ in range(10)]
return await asyncio.gather(*tasks)
if __name__ == '__main__':
cur_loop = asyncio.get_event_loop()
cur_loop.run_until_complete(main(cur_loop))
答案 0 :(得分:3)
asyncio
不能让您停止考虑并发问题。您遇到的线程问题几乎完全相同。
每个some_fun
协程看到伪造的self.val
值,并继续进入if
语句的主体。多少协程看到此值取决于在其中一个将if
设置为self.val
之前有多少协程达到10
测试。
在没有sleep
的情况下,第一个协程立即将self.val
设置为10
,而没有其他任何机会进行干预。有了sleep
,每个协程便进入睡眠状态并让其他人运行,并且所有人都在None
都未更改值之前看到它们。
答案 1 :(得分:2)
这与线程完全不一样。
所有协程都在同一线程中运行,协程中没有任何多线程竞争问题。
但是问题是协程的转换。当您使用await asyncio.sleep(1)
时,此等待将导致上下文从一个协程切换到另一个协程。
我们以两个协程为例:C1
和C2
。因此,起初有两个协程的执行队列:Q{C1, C2}
。然后弹出C1
来执行,如果没有await
,则没有任何切换,因此C1
将被完全执行。接下来,弹出C2
来执行。
因此,执行顺序为C1 -> C2
。这是完全线性的。
但是当有await
时,将导致切换。这意味着C1
将停止并插入到队列末尾。然后C2
将弹出执行。而C2
也将在该行停止并插入到队列末尾。接下来的C1
将再次弹出并完全执行。
所以执行的顺序是C1(before await) -> C2(before await) -> C1(the rest) -> C2(the rest)
。
很显然,检查val
后,所有协程将停止。这是核心问题。这不是有关并发竞争的问题,而是真正了解await
将如何影响您的程序的问题。