1 个不稳定版本
新版本 0.4.0 | 2024 年 8 月 16 日 |
---|
#255 在 异步
87 每月下载次数
25KB
276 行
moro-local
是 moro
的分支,在稳定 Rust 上运行,专注于本地(非 Send
)futures。
TL;DR
类似于 rayon 或 std::thread::scope
,moro 允许您使用 moro::async_scope!
宏创建一个 作用域。在这个作用域内,您可以启动可以访问作用域外定义的堆栈数据的作业
let value = 22;
let result = moro::async_scope!(|scope| {
let future1 = scope.spawn(async {
let future2 = scope.spawn(async {
value // access stack values that outlive scope
});
let v = future2.await * 2;
v
});
let v = future1.await * 2;
v
})
.await;
eprintln!("{result}"); // prints 88
堆栈访问,核心作用域 API
调用 moro::async_scope!(|scope| ...)
并返回整个作用域的 future,您可以使用 await 来启动作用域。
在作用域体 (...
) 中,您可以调用 scope.spawn(async { ... })
来启动一个作业。此作业必须在作用域自身完成之前终止。`scope.spawn` 的结果是作业返回的结果。
提前终止和取消
Moro作用域支持提前终止或取消。您可以通过调用scope.terminate(v).await
来终止作用域内的所有子线程。终止通常用于当v
是一个Result
,以取消Err
值(在导入模块中我们提供了如unwrap_or_cancel
这样的辅助方法)。
使用取消的一个例子可以在monitor中找到——在这个例子中,创建了多个任务,它们都检查输入中的一个整数。如果有任何整数是负数,则整个作用域将被取消。
未来工作:与类似rayon的迭代器集成
我想做这个。 :)
常见问题
moro
这个名字是从哪里来的?
它来自希腊语中“婴儿”(μωρό)这个词。流行的"trio"库使用“nursery”来指代作用域,因此我想尊重这一传统。
然而,“moros”也是即将到来的灾难的'hateful'精神,这是我之前不知道的,但感觉有点酷。
是否有其他异步子例程项目可用,moro如何比较?
是的!我知道...
async_nursery
,它与moro类似,但提供并行执行(而不仅仅是并发),但——作为结果——需要'static
约束。FuturesUnordered
,它可以作为某种子例程使用,但它也有一些已知的问题。这种类型目前在moro实现中使用,但moro的API阻止了这些问题发生。select
操作通常用于“模拟”并行流;与FuturesUnordered
一样,这是一个容易出错的途径,而moro部分地是为了作为select
-like API的替代品而演化的。
为什么moro产生的任务只并发运行,而不并行运行?
使用当前的Rust,并行运行的moro任务不能安全执行。详细内容将在后面的问题中说明,但简单地说,当moro作用域向其调用者让步时,作用域正在“放弃控制权”,而调用者可以选择——如果它选择的话——完全忘记作用域并停止执行它。这意味着如果moro作用域已经启动了并行线程,这些线程将继续访问调用者的数据,这可能会创建数据竞争。不太好。
并发运行不是有很大的限制吗?
有点吧?并行肯定很好,但对于许多异步服务器来说,您可以在连接之间获得并行性,而不需要在连接内部有并行性。您还可以使用其他机制来获得并行性,但需要'static
约束。
好吧,但为什么moro产生的任务只并发运行,而不并行运行?给我详细解释一下!
方法 Future::poll
允许安全代码“部分推进”一个未来,然后,因为未来是一个普通的Rust值,所以可以“忘记”它(例如,通过 std::mem::forget
,尽管还有其他方法)。这允许你创建一个作用域,执行几次,然后丢弃它而不运行任何析构函数
async fn method() {
let data = vec![1, 2, 3];
let some_future = moro::async_scope!(|scope| {
scope.spawn(async {
for d in &data {
tokio::task::yield_now().await;
}
});
});
// pseudo-code, we'd have to gin up a context etc:
std::future::Future::poll(some_future);
std::future::Future::poll(some_future);
std::mem::forget(some_future);
return;
}
如果moro任务并行运行,我们就没有办法确保在 method
返回之前停止作用域内创建的并行线程。结果,即使在栈帧弹出和数据释放后,它们仍然会访问 data
中的数据。不好。
但由于moro仅限于并发,这没关系。作用域内的任务只有在它们被轮询时才会推进(它们不是并行的)——所以当你“忘记”作用域时,你只是停止执行任务。
请注意,这个问题不会出现在像 rayon 或新的 std::thread::scope
调用这样的库中。这是因为同步代码具有异步代码所缺少的能力:同步函数可以阻塞其调用者(这是因为安全的Rust禁止longjmp)。但在异步代码中,在Rust的当前模型下,只要“await”某事,你就是在放弃对调用者的控制,他们可以自由地不再轮询你。这意味着,我相信,不可能有像moro那样的“作用域”,可以安全地引用作用域之外的数据,因为该数据属于你的调用者,你不能强迫他们不返回。换句话说,异步代码可以通过与执行器的合作,确保某些未来运行到完成。任何对 tokio::spawn
的调用都会做到这一点。但你不能确保你的未来是嵌入在某种运行到完成的东西中的。
我相信,没有修改 Future
特征或以某种方式修改Rust,就不能安全地启用并行执行。有许多建议要改变 Future
特征,以允许moro支持并行执行(这些相同的建议将有助于支持io-uring、DMA和其他功能),但前进的具体路径尚未确定。
依赖
~1–1.6MB
~33K SLoC