Share `Read` reference across threads

Continuing the discussion from Share reference across threads:

I am unable to share a std::io::Read across threads:

pub struct AsyncIO<'a> {
    
    send_thread: JoinGuard<'a, Mutex<&'a Read>>,
    recv_thread: JoinGuard<'a, Mutex<&'a Write>>,
    channels: Channels,
}
[…]
pub fn new<'a>
(producer: &Read, consumer: &Write, name: String) {
    
    let (tx, rx) = mpsc::channel();
    
    //let read_wrapper = Mutex::new(producer);
    let tx_thread = try!(
                    Builder::new()
                    .scoped(|| tx_thread_main(tx, producer)));
}

I tried putting the Read inside of an Arc<Mutex> and an Arc<Mutex<Box>>, nothing useful [1]. I always get the error message

error: the trait `core::marker::Sync` is not implemented for the type `std::io::Read + 'a`

Is this because of rust - static struct with raw pointer gives "`core::marker::Sync` is not implemented..." - Stack Overflow that raw pointer objects cannot be shared at all? Or am I missing something?

[1] multithreading - How can I share references across threads? - Stack Overflow

Here is a similiar but cleaner example:

pub fn new(producer: &Read, consumer: &Write, name: String) {
    
    let (tx, rx) = mpsc::channel();

    let read_wrapper = Arc::new(Mutex::new(producer));
    let x = read_wrapper.clone();
    let tx_thread = try!(
                    Builder::new()
                    .name(format!("Send thread: {}", name))
                    .scoped(move || tx_thread_main(tx, x)));
}

Still the same problems:

error: the trait `core::marker::Sync` is not implemented for the type `std::io::Read` [E0277]

note: `std::io::Read` cannot be shared between threads safely

Although I am NOT trying to share std::io::Read between threads but a Arc on a Mutex on a Reference on it.

To send Arc's across threads, Arc<T> needs to implement Send. To implement Send, Arc<T> requires that T implement Send and Sync. In your case, T is a Mutex<V>. For Mutex<V> to implement Send + Sync, V needs to implement Send. However, &Read doesn't implement Send.

To fix this, use &(Read + Sync) because &Sync implements Send. We need the type to be Sync because we are trying to share it across threads (&T is not an exclusive reference). However, you probably want &mut Read so you'll actually need &mut (Read + Send) because &mut Send implements Send. &mut Send can implement Send because &mut T is an exclusive reference.

For more information, read the Arc, Mutex, Send, and Sync API docs (specifically, the implementors section).

3 Likes

To put the Read in a Mutex<&'a mut (Read + Send)> I need a Read + Send before. How can I do that? Sadly neither the specification nor the Rust Programming Book specify how to do that.

Are asking why the Read implementors like File aren't Send?

First thing is I don't understand what the Generics syntax Read + Send is (means) and how such a type/variable could be created.
Second I don't understand how I can "box" a Read somehow that it is guaranteed to be thread-safe and can be Send. I think it is ok that Read implementations usually aren't Send.

&Trait or Box<Trait> are trait objects (this isn't generic programming BTW, because it involves dynamic dispatch). Trait Objects.

Since a trait only describes a subset of the object's behavior, you can combine them so a Box<Read + Send> is a trait object that implements both Read and Send.

File is Send (due to OBITOIBIT). It would be nice if rustdoc documented OBITOIBIT impls but you can always test if a type is Send:

use std::fs::File;

fn is_send<T: Send>() {}

fn main() {
    is_send::<File>();
}
2 Likes

I stand corrected then! :smile:

So this actually builds:

fn foo(x: std::fs::File) {
    bar(Box::new(x));
}

fn bar(x: Box<Read + Send>) { }
1 Like

Yes, that works for both types I need: std::fs::File and std::process::ChildStdin.

Btw: what is OBIT? Is it some kind of inherited implementation?

I believe, they're more often referred to as OIBIT. RFC

2 Likes

If I understand you correctly I can now specify the type Read + Send in a function header. I can call this function with parameters like std::fs::File or std::process::ChildStdin

I don't know the rationale to not make Read a Send, but that doesn't matter right now.

Yes, but also often you would do it in a generic way:

pub fn new<R: Read + Send, W: Write + Send>(
    producer: R, // a &Read (as opposed to &mut Read) is useless and you most likely want to take ownership of the reader
    consumer: W, // ditto
    name: &str) // if you're only going to pass the name to format, no need to own it
    -> YourStruct {
...
    

[quote="YBRyn, post:12, topic:1678"]
I don't know the rationale to not make Read a Send, but that doesn't matter right now.
[/quote]Some Read-able types aren't Send-able.

1 Like

Ok, that is pretty true.

1 Like