Can I safely multithread something which isn't

2019-08-17 07:59发布

I'm using a trait which isn't designed around multithreading (Cursive).

Now, while it's using multithreading, it's going to be behind a mutex, so it won't be able to be used at two threads at the same time.

What is rust trying to protect me against and can I do anything about it?

For sample reference, my sample code is:

extern crate cursive;

use cursive::Cursive;
use std::thread;
use std::sync::{Mutex,Arc};

fn main() {
    let mut siv = Arc::new(Mutex::new(Cursive::default()));
    let copy_siv = siv.clone();

    thread::spawn(move || {
        let mut new_siv = copy_siv.lock().unwrap();
    });

    (*(siv.lock().unwrap())).run();
 }

The compiler complains at thread::spawn:

   Error[E0277]: `(dyn cursive::traits::View + 'static)` cannot be sent between threads safely
   --> src/main.rs:16:5
   |
16 |     thread::spawn(move || {
   |     ^^^^^^^^^^^^^ `(dyn cursive::traits::View + 'static)` cannot be sent between threads safely
   |
   = help: the trait `std::marker::Send` is not implemented for `(dyn cursive::traits::View + 'static)`

2条回答
我只想做你的唯一
2楼-- · 2019-08-17 08:30

What is rust trying to protect me against [...]

Something in what you're sending between threads contains a dyn cursive::traits::View trait object. This trait object is not Send. It needs to be Send because by putting it inside an Arc, you can no longer predict which thread will be responsible for destroying it, so it must be safe to transfer ownership between threads.

[...] can I do anything about it?

You haven't provided enough context to say for certain, but probably not.

You could maybe try using a plain borrowed reference (plus a threading library that supports scoped threads), but I can't say if that will work for you.

Why wouldn't Mutex make it sync? Isn't that the point of Mutex?

No. It can't make something thread-safe when it wasn't already thread-safe. Mutex just manages exclusive access to a value, it doesn't make that access from different threads safe. The only thing that can make a type thread-safe is the type in question.

Making a guess: the library was written such that it does not require thread safety, thus Arc cannot assume it's thread-safe, so it refuses to compile.

查看更多
贼婆χ
3楼-- · 2019-08-17 08:47

I don't know what your actual code is. But the following example replicate the exact error you have:

use std::thread;
use std::sync::{Mutex,Arc};

struct Cursive;
impl Default for Cursive {
    fn default() -> Self {
        Cursive
    }
}
trait View{
    fn run(&self);
}
impl View for Cursive{
    fn run(&self){}
}

fn main() {
    let mut siv:Arc<Mutex<dyn View>> = Arc::new(Mutex::new(Cursive::default()));
    let copy_siv = siv.clone();

    thread::spawn(move || {
        let mut new_siv = copy_siv.lock().unwrap();
    });

    (*(siv.lock().unwrap())).run();
}

You can try it in playground. The error message:

error[E0277]: `dyn View` cannot be sent between threads safely
  --> src/main.rs:21:5
   |
21 |     thread::spawn(move || {
   |     ^^^^^^^^^^^^^ `dyn View` cannot be sent between threads safely
   |
   = help: the trait `std::marker::Send` is not implemented for `dyn View`
   = note: required because of the requirements on the impl of `std::marker::Send` for `std::sync::Mutex<dyn View>`
   = note: required because of the requirements on the impl of `std::marker::Send` for `std::sync::Arc<std::sync::Mutex<dyn View>>`
   = note: required because it appears within the type `[closure@src/main.rs:21:19: 23:6 copy_siv:std::sync::Arc<std::sync::Mutex<dyn View>>]`
   = note: required by `std::thread::spawn`

Analysis and Solution

The error message explained everything to experienced users. For those new to the language, siv is a reference counted, mutex protected trait object. This object only known to be a View, the compiler have no evidence on whether or not it is Send. However, for the code to work,

  • Arc<Mutex<T>> must be Send, as you are sending such a thing to another thread; Therefore:
  • Mutex<T> must be Send and Sync, as Arc requires the reference counted object to be Send and Sync. Therefore:
  • T must be Send, as the same object will be accessed in different threads without any further protection.

So, this code does not work. The solution is

let mut siv:Arc<Mutex<dyn View + Send>> = ...

You can try it yourself!

Mutex<T>: Send + Sync requires T: Send

To see why, first ask a question: what cannot be Send?

One example is that references to things with interior mutablity cannot be Send. Because if they were, people can mutate the thing through interior mutability in different threads and causes data race.

Now suppose you have a Mutex<&Cell<T>>, because the protected thing is only a reference, not the Cell itself, the Cell itself may still be somewhere unprotected. The compiler thus cannot conclude when you call lock().set() there is no risk to cause data race. So the compiler prevent it from Send.

What if I have to ...

So we see that &Cell<T> is not Send, and so even it is protected in Mutex we still cannot use it in another thread. What can we do then?

This problem is actually not new. Almost all UI API have the same problem: the UI components were created in the UI thread, and so you cannot access them in any other threads. Instead, you have to schedule a routine to be run in the UI thread, and let the UI thread to access the component.

Fails to do so in other languages (.NET, Java...) will throw exceptions in the best, causing undefined behavior in the worst. Once again, Rust turns such violates into compile errors without special treatments (&Cell<T> have nothing to do with UI), this is really GOOD!

So, if this is what you wanted to do, you have to do the same thing: access the view object in the UI thread only. How to do so depends on the API you were using.

查看更多
登录 后发表回答