Rayon 如何防止在线程之间使用 RefCell<T>、Cell<T> 和 Rc<T>?
How does Rayon prevent the use of RefCell<T>, Cell<T> and Rc<T> between threads?
Rayon 文档说它保证使用 Rayon API 不会引入数据竞争。
编译器如何知道闭包调用的方法不共享可变状态,例如 RefCell<T>
和 Cell<T>
,或者使用非线程安全的结构,例如 Rc<T>
?
我知道 core::marker::Sync
标记了可以在线程之间安全共享的类型,但我不明白 Rayon 类型声明和编译器如何强制执行它!
你实际上自己回答了你的问题——所有需要在线程之间共享的闭包都需要是 Sync
,而 Rayon 的 API 只是通过特征边界要求它们是 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()
参数的特征绑定,但它仍然会向您指出相关方法,并解释说它期望闭包是 Sync
,以及它不是的原因。
Rayon 文档说它保证使用 Rayon API 不会引入数据竞争。
编译器如何知道闭包调用的方法不共享可变状态,例如 RefCell<T>
和 Cell<T>
,或者使用非线程安全的结构,例如 Rc<T>
?
我知道 core::marker::Sync
标记了可以在线程之间安全共享的类型,但我不明白 Rayon 类型声明和编译器如何强制执行它!
你实际上自己回答了你的问题——所有需要在线程之间共享的闭包都需要是 Sync
,而 Rayon 的 API 只是通过特征边界要求它们是 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()
参数的特征绑定,但它仍然会向您指出相关方法,并解释说它期望闭包是 Sync
,以及它不是的原因。