Rayon 如何防止线程之间使用 RefCell<T>、Cell<T> 和 Rc<T>?

Posted

技术标签:

【中文标题】Rayon 如何防止线程之间使用 RefCell<T>、Cell<T> 和 Rc<T>?【英文标题】:How does Rayon prevent the use of RefCell<T>, Cell<T> and Rc<T> between threads? 【发布时间】:2019-05-03 15:37:21 【问题描述】:

Rayon 文档说它保证使用 Rayon API 不会引入数据竞争。

编译器如何知道闭包调用的方法不共享可变状态,例如RefCell&lt;T&gt;Cell&lt;T&gt;,或者使用不是线程安全的结构,例如Rc&lt;T&gt;

我知道core::marker::Sync 标记了可以在线程之间安全共享的类型,但我不明白 Rayon 类型声明和编译器如何强制执行它!

【问题讨论】:

README 开头的字面意思是“您可能还喜欢 this blog post 关于 Rayon,它提供了有关其工作原理的更多背景和详细信息” 我能找到的只是它使用 Join 将闭包类型定义为“FnOnce() -> R_A + Send”,但这并不能解释为什么这种类型的闭包不会使用任何 RefCell内部! 【参考方案1】:

实际上,您自己回答了您的问题——所有需要在线程之间共享的闭包都必须是 Sync,而 Rayon 的 API 只是通过 trait bound 要求它们是 Sync。参见例如documentation of ParallelIterator::map(),它将方法指定为

fn map<F, R>(self, map_op: F) -> Map<Self, F> where
    F: Fn(Self::Item) -> R + Sync + Send,
    R: Send, 

这里没有任何更深层次的魔法——每当 Rayon 以一种要求它为 Sync 的方式使用闭包时,例如通过将其传递给较低级别​​的 API,Rayon 使用 Sync 特征绑定来限制相应的参数类型。这反过来确保闭包中存储的所有内容都是Sync,因此您不能在闭包中存储任何RefCell

在这种情况下,您也可以向编译器询问解释。例如,如果您尝试编译此代码

use std::cell::RefCell;
use rayon::prelude::*;

fn main() 
    let c = RefCell::new(5);
    let _ = [1, 2, 3]
        .par_iter()
        .map(|i| i * *c.borrow())
        .sum();

您将收到此错误 (playground)

error[E0277]: `std::cell::RefCell<i32>` cannot be shared between threads safely
  --> src/main.rs:10:10
   |
10 |         .map(|i| i * *c.borrow())
   |          ^^^ `std::cell::RefCell<i32>` cannot be shared between threads safely
   |
   = help: within `[closure@src/main.rs:10:14: 10:33 c:&std::cell::RefCell<i32>]`, the trait `std::marker::Sync` is not implemented for `std::cell::RefCell<i32>`
   = note: required because it appears within the type `&std::cell::RefCell<i32>`
   = note: required because it appears within the type `[closure@src/main.rs:10:14: 10:33 c:&std::cell::RefCell<i32>]`

虽然编译器很遗憾没有直接提及 map() 的参数的 trait bound,但它仍然指向相关方法,并解释它期望闭包是 Sync,以及它的原因不。

【讨论】:

只说到同步/发送,有没有Sync 不是Send 的类型? @soupybionics 当然你可以定义一个,但标准库中也有一些,例如MutexGuard. 谢谢。另外,如果一个类型是SyncCopy,那么它没有理由不能是Send,对吧? @soupybionics 对于SyncCopy 但不是Send 的类型,我看不到一个好的用例。似乎如果您可以共享参考然后复制该值,您也应该被允许直接发送该值,但有时有些微妙之处并不立即显而易见,所以我不会将此作为证明可以'不是这样的人。

以上是关于Rayon 如何防止线程之间使用 RefCell<T>、Cell<T> 和 Rc<T>?的主要内容,如果未能解决你的问题,请参考以下文章

如何正确访问 RefCell 中的值

如何在不破坏封装的情况下返回对 RefCell 内某些内容的引用?

如何将 Rc<RefCell<_>> 中的具体对象转换为动态特征对象? [复制]

如何借用 RefCell<HashMap>、找到键并返回对结果的引用? [复制]

是否可以结合 Rayon 和 Faster?

如何将 Rc<RefCell<dyn T>> 传递给想要 &dyn T 的 fn?