- 原文:[https://without.boats/blog/the-scoped-task-trilemma/](https://without.boats/blog/the-scoped-task-trilemma/) - 背景: - 移植 scoped thread API 到 [scoped tasks](https://tmandry.gitlab.io/blog/posts/2023-03-01-scoped-tasks/) - 发现 API 的几个期望特性最终彼此之间是不兼容的。 - 原因在于 Rust 现有类型系统的局限性。 - scoped task trilemma,但不局限于 scoped tasks - **Concurrency**: child tasks 和 **parent** 可以并发进行 - **Parallelizability**:child tasks 和 **parent** 可以并行执行 - **Borrowing**:child tasks 可以从 parent 中 borrow data,而不需要同步(synchronization)。 - 三个特性无法同时满足,导致 scoped task API 不是 sound 的、io-uring等特性无法实现。 - 每个 sound 的 concurrency API 最终只提供了三个特性中的两个。 ---- # Concurrency + Parallelizability - 代表例子: - **`thread::spawn`** - **`task::spawn`** - 这些 APIs 允许 child task 与 parent task 独立且并发执行。 - child task 可以在另外一个 thread 上执行,可能是 in parallel,同时不会打断 parent task 的执行。 - 另一方面,child task 无法从 parent task 进行 borrow,这些 APIs 的参数都必须是 `'static` 的。 - 这些 APIs 允许 join child task 来等待它完成,但不是必须要这么做。 - 像异步的 `task::spawn` API ,当等待时,你可以在相同线程上运行当前作用域之外的 task。 - 即 executor 的线程,运行其他代码 spwan 的 task ------ # Parallelizability + Borrowing - 代表例子: - **`thread::scope`** - **`rayon::join`** - 这些 APIs 允许 child task 跟 parent 独立处理,同时从 parent 进行 borrowing。 - child task 可以在另外的线程执行,同时从 parent context 中 borrowing。 - 限制:child scope 和 parent scope 之间的代码无法并发运行(concurrently)。 - 这些 APIs 强制要求你在返回控制权给 caller 之前必须 join child task,这是他们确保跨线程 borrowing 符合 lifetime signature 的方式。 --- # Borrowing + Concurrency - 代表例子: - **`select!`** - **`FuturesUnordered`** - 这些 APIs 可以实现 child tasks 跟 parent task 并发(concurrently)处理,同时可以从 parenet borrowing。 - 限制:child tasks 不能独立于 parent task 运行,这些 APIs 没有 joining 的概念。 - 被 selected 或 streamed 的 futures 作为与 parent 相同 work unit 的一部分运行。 - 并且在执行期间不能 move 到另一个线程,除非与 parent 作为一个单独的单元一起 move。 ---- # Why not all three? - scoped thread 和 rayon APIs 可以同时支持 parallelizable 和 borrowing,而无需同步。实际上是需要同步的: - 同步发生在这些 APIs join 子线程时,返回 caller 之前。 - 然而,这种同步被摊销为函数结束时的单个等待操作,而不需要某种同步来共享或传递所有状态给不同的任务。 - 旧版的 scoped thread API 组织上是不同的,它是当 join handle 被 drop 时才进行 join。 - 旧版的 API 是 unsound 因为 Rust 中的对象可以 Leak 出去,而不需要调用析构函数。 - [Leakpocalypse](https://cglab.ca/~abeinges/blah/everyone-poops/#leakpocalypse)问题 - 也就是为什么最近在讨论支持不能被 Leak 的类型。最大问题是会破坏向后兼容。 - 在新的 scoped thread API 中,interior scope(可以与 parallelized tasks 并发执行)是由传递给 `scope` 函数的闭包在词法上定义的, exterior scope 是在此闭包之外的任何东西。 - 在老的 scoped thread API 中,interior scope 的定义更隐式,通过 child thread join handle 存活的代码段。 --- # Why not just two?