引子: thread构建子进程, 并利用channel的一般方式如下:
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn( move || {
let val = String::from("hi");
tx.send(val).unwrap();
});
let received = rx.recv().unwrap();
println!("Got: {}", received);
}
接下来是一个没事找事的操作, 如果把 move
关键字去掉, 会发生什么事呢? 编译器报错如下:
error[E0277]: `std::sync::mpsc::Sender<std::string::String>` cannot be shared between threads safely
--> src/main.rs:7:5
|
7 | thread::spawn( || {
| ^^^^^^^^^^^^^ `std::sync::mpsc::Sender<std::string::String>` cannot be shared between threads safely
|
= help: the trait `std::marker::Sync` is not implemented for `std::sync::mpsc::Sender<std::string::String>`
= note: required because of the requirements on the impl of `std::marker::Send` for `&std::sync::mpsc::Sender<std::string::String>`
= note: required because it appears within the type `[closure@src/main.rs:7:20: 10:6 tx:&std::sync::mpsc::Sender<std::string::String>]`
本文就是想知道, 为什么去掉move会报这个编译错误。
场景1 分析: 没有move的情况
thread::spawn( || {
let val = String::from("hi");
tx.send(val).unwrap();
});
这里的thread::spawn 里的closure没有加 move
说明这里的tx是reference, 而不是有ownership的type。
又因为Sender实现了send, 结合现在tx的类别, 所以可以推断: &std::sync::mpsc::Sender<std::string::String>
也实现了 Send (这里前面是 ‘&’ 是因为 tx 没有所有权, 即上面说的reference)
既然 &std::sync::mpsc::Sender<std::string::String>
实现了 Send, 那么可以继续推断, std::sync::mpsc::Sender<std::string::String>
应该实现了 Sync 【这里的依据是 if &T is Send, then T is Sync】
现在冲突来了, 上面的检查逻辑需要Sender是实现了Sync, 而实际Send里是明确禁止了Sync, 也即不支持:(源码如下:)
#[stable(feature = "rust1", since = "1.0.0")]
unsafe impl<T: Send> Send for Sender<T> {}
#[stable(feature = "rust1", since = "1.0.0")]
impl<T> !Sync for Sender<T> {}
场景2 分析: 有move的情况
thread::spawn( move|| {
let val = String::from("hi");
tx.send(val).unwrap();
});
这里有了move, 表示tx的所有权move到子线程
因为Sender实现了send, 结合tx的类别(独立所有权), 可以推断: std::sync::mpsc::Sender<std::string::String>
也实现了 Send
到这一步, 发现Sender没有对Sync有任何要求, 所以没有触发冲突。
这是我目前感觉从编译器的角度逻辑最简单的解释, 感觉编译器的逻辑是简单的数学逻辑, 有明确的原因和结果, 但是我听大家来分析的话, 感觉就像听天书。
下一步action: 以后出错, 多从源码和编译器的角度看逻辑, 也许会柳暗花明?
当然, 上面的具体分析是否正确我也不确定, 欢迎大家反馈。
网友评论