为什么在将包含 Arc 的自身移动到新线程时出现"不满足同步"的错误?



我有一个保存Arc<Receiver<f32>>的结构,我正在尝试添加一个获取self所有权的方法,并将所有权移动到一个新线程中并启动它。但是,我收到错误

error[E0277]: the trait bound `std::sync::mpsc::Receiver<f32>: std::marker::Sync` is not satisfied
--> src/main.rs:19:9
|
19 |         thread::spawn(move || {
|         ^^^^^^^^^^^^^ `std::sync::mpsc::Receiver<f32>` cannot be shared between threads safely
|
= help: the trait `std::marker::Sync` is not implemented for `std::sync::mpsc::Receiver<f32>`
= note: required because of the requirements on the impl of `std::marker::Send` for `std::sync::Arc<std::sync::mpsc::Receiver<f32>>`
= note: required because it appears within the type `Foo`
= note: required because it appears within the type `[closure@src/main.rs:19:23: 22:10 self:Foo]`
= note: required by `std::thread::spawn`

如果我更改结构以保存Arc<i32>,或者只是一个Receiver<f32>,它会编译,但不带有Arc<Receiver<f32>>。这是如何工作的?该错误对我来说没有意义,因为我没有尝试在线程之间共享它(我正在移动它,而不是克隆它(。

以下是完整的代码:

use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::Arc;
use std::thread;
pub struct Foo {
receiver: Arc<Receiver<f32>>,
}
impl Foo {
pub fn new() -> (Foo, Sender<f32>) {
let (sender, receiver) = channel::<f32>();
let sink = Foo {
receiver: Arc::new(receiver),
};
(sink, sender)
}
pub fn run_thread(self) -> thread::JoinHandle<()> {
thread::spawn(move || {
println!("Thread spawned by 'run_thread'");
self.run(); // <- This line gives the error
})
}
fn run(mut self) {
println!("Executing 'run'")
}
}
fn main() {
let (example, sender) = Foo::new();
let handle = example.run_thread();
handle.join();
}

这是如何工作的?

让我们再次检查thread::spawn的要求:

pub fn spawn<F, T>(f: F) -> JoinHandle<T> 
where
F: FnOnce() -> T,
F: Send + 'static,   // <-- this line is important for us
T: Send + 'static, 

由于Foo包含一个Arc<Receiver<_>>,让我们检查一下Arc是否以及如何实现Send

impl<T> Send for Arc<T> 
where
T: Send + Sync + ?Sized,

所以Arc<T>实现Send如果T实现SendSync.虽然Receiver实现了Send,但它没有实现Sync

那么Arc为什么对T有这么强的要求呢?T还必须实现SendArc因为它可以像容器一样运行;如果您可以隐藏在Arc中无法实现Send的东西,请将其发送到另一个线程并在那里解压缩......坏事会发生。有趣的部分是看看为什么T还必须实现Sync,这显然也是您正在努力解决的部分:

该错误对我来说没有意义,因为我没有尝试在线程之间共享它(我正在移动它,而不是克隆它(。

编译器无法知道Foo中的Arc实际上不是共享的。考虑以后是否会向Foo添加#[derive(Clone)](这可以没有问题(:

fn main() {
let (example, sender) = Foo::new();
let clone = example.clone();
let handle = example.run_thread();
clone.run();
// oopsie, now the same `Receiver` is used from two threads!
handle.join();
}

在上面的示例中,只有一个Receiver在线程之间共享。这不好,因为Receiver不实施Sync

对我来说,这段代码提出了一个问题:为什么首先要Arc?正如您所注意到的,如果没有Arc,它可以毫无问题地工作:您明确指出FooReceiver的唯一所有者。而且,如果您"无论如何都不想共享[接收器]",那么拥有多个所有者是没有意义的。

最新更新