【问题标题】:Await call causing unexpected behavior on instance attribute等待调用导致实例属性出现意外行为
【发布时间】:2018-12-05 06:18:33
【问题描述】:

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

【问题讨论】:

    标签: python async-await python-asyncio


    【解决方案1】:

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

    每个看到 self.val 虚假值的 some_fun 协程继续进入 if 语句的主体。有多少协程看到这样的值取决于有多少协程到达if 测试,然后其中一个将self.val 设置为10

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

    【讨论】:

    • 这是一个非常基本的问题。我无法弄清楚。我将使用某种同步来初始化值,然后让所有并发工作完成。
    【解决方案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 将如何影响您的程序的问题。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-04
      • 1970-01-01
      • 2021-07-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多