Future trait 实现是否"force"你打破 rust 中的 noalias?



我会考虑 rust 异步基础设施,API 的核心是 Future 特征,即:

pub trait Future {
type Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}

根据文档

未来的核心方法,poll,试图将未来解析为最终值。如果值未就绪,则此方法不会阻止。相反,当前任务计划在可以通过再次轮询取得进一步进展时唤醒。传递给轮询方法的上下文可以提供Waker,这是唤醒当前任务的句柄。

这意味着传递给.poll()(特别是唤醒器)的Context值需要某种方式来引用固定的未来&mut self才能唤醒它。但是&mut意味着引用没有锯齿。我是否误解了什么,或者这是允许混叠&mut的"特殊情况"?如果是后者,除了UnsafeCell之外,还有其他这样的特殊情况吗?

需要某种方法来引用固定的未来&mut self才能唤醒它。

不,它没有。要理解的关键是,"任务"不是未来:它是未来以及执行者对它的了解。唤醒者究竟变异了什么取决于执行者,但它一定不是Future的东西。我说"必须"不仅仅是因为 Rust 可变性规则,还因为Future不包含任何说明它们是否已被唤醒的状态。所以,那里没有什么可以有用的变异;未来内存的 100% 字节专用于特定的Future实现,与执行者无关。

那么在下一页,如果这本书你会注意到该任务包含一个盒装的未来,并且唤醒器是从对任务的引用创建的。因此,从任务中提到了未来,尽管是间接的。

好的,让我们看一下这些数据结构。浓缩:

struct Executor {
ready_queue: Receiver<Arc<Task>>,
}
struct Task {
future: Mutex<Option<BoxFuture<'static, ()>>>,
task_sender: SyncSender<Arc<Task>>,
}

对任务的引用是一个Arc<Task>,未来本身在任务的一个Mutex(内部可变性)中。因此

不可能
  1. Arc<Task>获得&mut Task,因为Arc不允许这样做。
  2. future位于一个Mutex中,该执行运行时检查是否最多有一个可变引用。

您唯一能用Arc<Task>做的事情是

  • 克隆并发送
  • &访问Mutex中的future(允许请求Future进行运行时检查的突变访问)
  • 获得对task_sender&的访问权限(允许将内容发送到ready_queue)。

因此,在这种情况下,当唤醒器被调用时,它甚至根本不会改变任何特定于Task的东西:它会克隆Arc<Task>(增加存储在Task旁边的原子引用计数)并将其放在ready_queue上(这会改变SenderReceiver之间共享的存储)。

另一个执行程序可能确实在Task中具有特定于任务的状态,该状态已发生突变,例如标记任务已唤醒且不需要再次唤醒的标志。该标志可能存储在任务的AtomicBoolean字段中。但是,它仍然没有与Future的任何&mut混叠,因为它不是Future的一部分,而是任务。


综上所述,Futures和noalias实际上有一些特别之处 - 但这不是关于执行者,而是关于Pin。固定显式允许固定类型包含指向自身的"自引用"指针,因此 Rust 不会为Pin<&mut T>声明 noalias。然而,围绕这一点的语言规则究竟是什么仍然没有严格规定;我认为,目前的情况只是被认为是一个难题,以便可以正确编译async函数。

没有这样的特殊情况。从你对 Rust 执行器的评论来看,你误解了内部可变性是如何工作的。

Rust 书中的示例使用Arc包装的Task结构,未来包含在Mutex中。运行任务时,它会锁定互斥锁并获取允许存在的单数&mut引用。

现在看看该示例如何实现wake_by_ref,并注意它如何从不触及future。事实上,该功能根本无法锁定未来,因为上层已经锁定了。它将无法安全地获取&mut引用,因此它会阻止您这样做,因此没有问题。

UnsafeCell及其包装器的限制是,在任何时间点,一个对象只能存在一个&mut引用。但是,可能存在多个&不可变的引用,指向包含它的UnsafeCell或结构 - 这就是内部可变性的重点。

相关内容

  • 没有找到相关文章

最新更新