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

Is it safe that when Two asyncio tasks access the same awaitable object?

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

但是,不止一个 Asyncio Task 可以像 multi-threading 一样一次多次访问资源。

例如DB连接(如果对象不是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 中是否完全安全?如果是,它的安全性如何?

不,asyncio 不是线程安全的。通常只有一个线程应该控制事件循环 and/or 与事件循环关联的资源。如果其他线程想要访问它,它应该通过特殊方法来访问它,比如 call_soon_threadsafe.

从多个任务中使用相同的异步对象通常是安全的。例如,aiohttp 有一个会话对象,多个任务应该“并行”访问同一个会话。

if so, what does it make safe?

asyncio 的基本架构允许多个协程 await 一个单一的未来结果——它们将简单地全部订阅未来的完成,并且一旦结果是所有将被安排到 运行准备好。这不仅适用于协程,还适用于使用 add_done_callback.

订阅未来的同步代码

这就是 asyncio 处理您的场景的方式:任务 A 和 B 最终将订阅 DB 对象等待的某个未来。一旦结果可用,它将依次传递给他们两个。

通常与 multi-threaded 编程相关的陷阱不适用于 asyncio,因为:

  • 与线程不同,上下文切换发生的位置非常容易预测 - 只需查看代码中的 await 语句(以及 async withasync for - 但这些仍然是非常明显的关键字)。就所有意图和目的而言,它们之间的任何内容都是原子的。这消除了使用同步原语来保护对象的需要,以及因处理此类工具不当而导致的错误。

  • 所有对数据的访问都发生在 运行 事件循环的线程中。这消除了 数据竞争 读取正在同时写入的共享内存的可能性。

multi-tasking 可能失败的一种情况是多个消费者附加到同一个 stream-like 资源。例如,如果多个任务尝试在同一个 reader 流上等待 reader.read(n),则其中一个将获得新数据 1,而其他任务将保持等待新数据到来。这同样适用于任何共享的流媒体资源,包括文件描述符或由多个对象共享的生成器。而且即便如此,其中一个任务也能保证获取到数据,流对象的完整性不会受到任何损害。


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