#real-time #async #async-task #data-structures #os

lilos-handoff

lilos的同步 rendezvous 结构

4 个版本 (2 个稳定版)

1.0.1 2024年4月26日
1.0.0 2024年4月23日
1.0.0-pre.12024年3月3日

#706 in 嵌入式开发

Download history 372/week @ 2024-04-22 12/week @ 2024-04-29

每月 226次下载

MPL-2.0 许可证

180KB
1.5K SLoC

lilos的handoff结构

实现了同步 rendezvous 结构,允许任务在不进行额外复制或预留存储空间的情况下将值传递给另一个任务。

这原本是 lilos 核心API的一部分,但在最终确定 lilos 1.0 版本的过程中被提取出来。它目前与 lilos 分离,因为它的API不是取消安全的。

尽管不是取消安全的,但仍然非常有用。请参阅模块文档以获取更多详细信息。


lib.rs:

从一项任务向另一项任务传递数据的最小化复制机制。

这个crate为 lilos 提供了 Handoff 抽象。

Handoff 有两个方面:发送者和接收者。当发送者和接收者都准备好时,一个单一的 T 会从发送者的所有权传递到接收者。在这种情况下,“准备好”意味着发送者或接收者已经因为等待对方而被阻塞,当对方到达时——当两个任务都在handoff处等待时,我们可以复制数据然后解除两个任务的阻塞。

因为我们不需要任何 T 的复制区域,所以 Handoff 非常小——大约是两个指针的大小。

在计算机科学中,这被称为 rendezvous,但拼写起来比handoff困难。

创建和使用 Handoff

Handoff 本身不包含任何存储,因此它们在栈上创建很便宜。然后你需要将它们 split 成它们的 PusherPopper 端——这两个都 borrowHandoff,所以你需要保留它。然后你可以将端点传递给其他future。一个典型的用例如下

let mut handoff = Handoff::new();
let (push, pop) = handoff.split();
join!(data_producer(push), data_consumer(pop));

如果您只想在 rendezvous 点同步两个任务,且不需要移动数据,请使用 Handoff<()>。它能够正确地完成工作。

注意事项和替代方案

一次只能存在一个 PusherPopper -- 编译器确保了这一点。这极大地简化了实现过程,但这也意味着如果您需要多方的 rendezvous,这不是合适的工具。

如果您希望能够推送数据并继续处理,而不必等待其被弹出,您需要一个队列而不是 handoff。请参阅 lilos::spsc 模块。

请注意,这些类型都不是 SendSync -- 它们绝对不是线程安全的,因此可以在 async 任务中自由使用,但不能与中断处理程序共享。出于同样的原因,您可能不想尝试将其存储在 static 中 -- 即使使用足够的 unsafe,结果也将无济于事!在 spsc 中提供的队列没有这个限制,但代价是设置起来更麻烦。

取消安全性

Handoff 并非严格取消安全,与 lilos 中的大多数内容不同。具体来说,在 pushpop future 解决之前取消它们,最多会导致丢失一个数据项。

虽然技术上是非取消安全的,但由于实际使用 handoff 的方式,这通常是可以接受的。请仔细阅读 Pusher::pushPopper::pop 的文档,否则您可能会丢失数据。

如果 handoff 的推送和弹出端是“长期存在”的,由不会取消的任务(如 lilos 中的顶层任务)持有,并且永远不会在可能会取消未来的上下文中使用(如 with_timeout),那么您不需要担心这个问题。尽管这并不是编译器可以检查的性质,所以再次提醒 -- 小心行事。

依赖关系

~1–1.5MB
~24K SLoC