当两个异步任务访问同一个可等待对象时是否安全?
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
操作)。
- 安排
Task A
和 Task B
访问同一个数据库对象。
- IO循环执行
Task A
.
Task A
await
对DB对象的IO操作。(需要足够长的时间)
- IO循环执行
Task B
Step3
的IO操作还在进行中(未完成)。
Task B
await
对同一个 DB 对象的 IO 操作。
- 现在
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 with
和 async 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
的每个任务中等待它, 所有 任务将被通知该未来最终返回的特定数据块。
简单来说,thread-safe
意味着当多个 thread
访问同一资源时是安全的,我知道 Asyncio
从根本上使用单个 thread
。
但是,不止一个 Asyncio Task
可以像 multi-threading
一样一次多次访问资源。
例如DB连接(如果对象不是thread-safe
并且支持Asyncio
操作)。
- 安排
Task A
和Task B
访问同一个数据库对象。 - IO循环执行
Task A
. Task A
await
对DB对象的IO操作。(需要足够长的时间)- IO循环执行
Task B
Step3
的IO操作还在进行中(未完成)。Task B
await
对同一个 DB 对象的 IO 操作。- 现在
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 with
和async 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
的每个任务中等待它, 所有 任务将被通知该未来最终返回的特定数据块。