Почему я получаю сообщение об ошибке «Синхронизация не выполняется» при перемещении себя, содержащего дугу, в новый поток? - PullRequest
0 голосов
/ 30 апреля 2018

У меня есть структура, которая содержит 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();
}

1 Ответ

0 голосов
/ 30 апреля 2018

Как это работает?

Давайте проверим требования 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 реализует Send и Sync. И хотя Receiver реализует Send, он не реализует Sync.

Так почему Arc предъявляет такие строгие требования к T? T также должен реализовывать Send, потому что Arc может действовать как контейнер; если бы вы могли просто спрятать что-то, что не реализует Send в Arc, отправьте это в другой поток и распакуйте его там ... могут случиться плохие вещи. Интересная часть состоит в том, чтобы понять, почему T также должен реализовывать Sync, что, очевидно, также является частью, с которой вы боретесь:

Ошибка не имеет смысла для меня, поскольку я не пытаюсь разделить ее между потоками (я перемещаю ее, а не клонирую).

Компилятор не может знать, что Arc в Foo фактически не является общим. Подумайте, хотите ли вы добавить #[derive(Clone)] к Foo позже (что возможно без проблем):

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 он работает без проблем: вы четко заявляете, что Foo является единственным владельцем Receiver. И если вы «не пытаетесь поделиться [Получателем]» в любом случае, нет смысла иметь несколько владельцев.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...