等待调用导致实例属性发生意外行为

时间:2018-12-04 21:39:42

标签: python async-await python-asyncio

我很难弄清楚为什么不注释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))

2 个答案:

答案 0 :(得分:3)

asyncio不能让您停止考虑并发问题。您遇到的线程问题几乎完全相同。

每个some_fun协程看到伪造的self.val值,并继续进入if语句的主体。多少协程看到此值取决于在其中一个将if设置为self.val之前有多少协程达到10测试。

在没有sleep的情况下,第一个协程立即将self.val设置为10,而没有其他任何机会进行干预。有了sleep,每个协程便进入睡眠状态并让其他人运行,并且所有人都在None都未更改值之前看到它们。

答案 1 :(得分:2)

这与线程完全不一样。

所有协程都在同一线程中运行,协程中没有任何多线程竞争问题。

但是问题是协程的转换。当您使用await asyncio.sleep(1)时,此等待将导致上下文从一个协程切换到另一个协程。

我们以两个协程为例:C1C2。因此,起初有两个协程的执行队列: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将如何影响您的程序的问题。