当两个 asyncio 任务访问同一个可等待对象时是否安全?

San*_*Kim 7 python thread-safety python-3.x python-asyncio

简单地说,thread-safe意味着当多个人thread访问相同的资源并且我知道从根本上Asyncio使用一个时是安全的thread

但是,不止一个人Asyncio Task可以同时多次访问资源,例如multi-threading.

例如数据库连接(如果对象不是thread-safe并且支持Asyncio操作)。

  1. 调度Task ATask B访问同一个数据库对象。
  2. IO 循环执行Task A
  3. Task A await DB对象上的IO操作。(需要很长时间)
  4. IO 循环执行 Task B
  5. Step3的 IO 操作仍在进行中(未完成)。
  6. Task B await 对同一个 DB 对象的 IO 操作。
  7. 现在Task B试图一次访问同一个对象。

它是完全Asyncio安全的吗?如果是,它是如何保证安全的?

use*_*342 5

Using the same asyncio object from multiple tasks is safe in general. As an example, aiohttp has a session object, and it is expected for multiple tasks to access the same session "in parallel".

if so, what does it make safe?

The basic architecture of asyncio allows for multiple coroutines to await a single future result - they will simply all subscribe to the future's completion, and all will be scheduled to run once the result is ready. And this applies not only to coroutines, but also to synchronous code that subscribes to the future using add_done_callback.

That is how asyncio will handle your scenario: tasks A and B will ultimately subscribe to some future awaited by the DB object and. Once the result is available, it will be delivered to both of them, in turn.

Pitfalls typically associated with multi-threaded programming do not apply to asyncio because:

  • Unlike with threads, it is very predictable where a context switch can occur - just look at await statements in the code (and also async with and async for - but those are still very visible keywords). Anything between them is, for all intents and purposes, atomic. This eliminates the need for synchronization primitives to protect objects, as well as the mistakes that result from mishandling such tools.

  • All access to data happens from the thread that runs the event loop. This eliminates the possibility of a data race, reading of shared memory that is being concurrently written to.

One scenario in which multi-tasking could fail is multiple consumers attaching to the same stream-like resource. For example, if several tasks try to await reader.read(n) on the same reader stream, exactly one of them will get the new data1, and the others will keep waiting until new data arrives. The same applies to any shared streaming resource, including file descriptors or generators shared by multiple objects. And even then, one of the tasks is guaranteed to obtain the data, and the integrity of the stream object will not be compromised in any way.


1一个接收数据的任务仅适用于任务共享阅读器并且每个任务单独调用的情况data = await reader.read(n)。如果要使用(使用)提取未来,在多个任务之间共享未来,并在每个任务中等待它,所有任务都将收到最终由该未来返回的特定数据块的通知。fut = asyncio.ensure_future(reader.read(n))awaitdata = await fut

  • @SangminKi​​m 很可能有这样的错误。程序员可能会忘记 `await` 是一个(可能的)上下文切换,并且错误地依赖于在跨越 `await` 时没有被修改的对象状态,而实际上另一个任务改变了它。这样的事情发生。但是您的问题明确将协程与多线程代码进行了比较,因此我的回答突出了根本差异。在多线程中,上下文切换可以出现在任何地方,并且每个 CPU 都使用自己缓存的一部分 RAM 副本,因此必须使用锁和屏障保护对共享数据的*所有*访问。 (2认同)