Warm tip: This article is reproduced from serverfault.com, please click

How to pass a struct with Rc fields between threads (without simultaneous access)

发布于 2020-11-28 08:26:09

I have the following (simplified) code that spawns a few threads that do long and complex operations to build a Transactions struct. This Transactions struct contains fields with Rc. At the end of the threads I want to return the computed Transactions struct to the calling thread through an mpsc::channel.

use std::thread;
use std::collections::HashMap;
use std::sync::mpsc::{channel, Sender};
use std::rc::Rc;

#[derive(Debug)]
struct Transaction {
  id: String,
}

#[derive(Debug)]
struct Transactions {
  list: Vec<Rc<Transaction>>,
  index: HashMap<String, Rc<Transaction>>,
}

fn main() {
  
  let (tx, rx) = channel();
  
  for _ in 0..4 {
    tx = Sender::clone(&tx);
    thread::spawn(move || {
      // complex and long computation to build a Transactions struct
      let transactions = Transactions { list: Vec::new(), index: HashMap::new() };
      tx.send(transactions).unwrap();
    });
  }
  
  drop(tx);

  for transactions in rx {
    println!("Got: {:?}", transactions);
  }

}

The compiler complains that std::rc::Rc<Transaction> cannot be sent safely between threads because it does not implement the std::marker::Send trait.

error[E0277]: `std::rc::Rc<Transaction>` cannot be sent between threads safely
   --> src/main.rs:23:5
    |
23  |     thread::spawn(move || {
    |     ^^^^^^^^^^^^^ `std::rc::Rc<Transaction>` cannot be sent between threads safely
    |
    = help: the trait `std::marker::Send` is not implemented for `std::rc::Rc<Transaction>`
    = note: required because of the requirements on the impl of `std::marker::Send` for `std::ptr::Unique<std::rc::Rc<Transaction>>`
    = note: required because it appears within the type `alloc::raw_vec::RawVec<std::rc::Rc<Transaction>>`
    = note: required because it appears within the type `std::vec::Vec<std::rc::Rc<Transaction>>`
    = note: required because it appears within the type `Transactions`
    = note: required because of the requirements on the impl of `std::marker::Send` for `std::sync::mpsc::Sender<Transactions>`
    = note: required because it appears within the type `[closure@src/main.rs:23:19: 27:6 tx:std::sync::mpsc::Sender<Transactions>]`

I understand the I could replace Rc by Arc, but was looking to know if there was any other solution to avoid the performance penalty of using Arc, because the Rc structs are never accessed by two threads at the same time.

Questioner
quentinadam
Viewed
0
Mihir 2020-11-30 10:49:59

Unfortunately I can't delete this post as this is the accepted answer but I want to point to this link that I missed before:

Is it safe to `Send` struct containing `Rc` if strong_count is 1 and weak_count is 0?

Since Rc is not Send, its implementation can be optimized in a variety of ways. The entire memory could be allocated using a thread-local arena. The counters could be allocated using a thread-local arena, separately, so as to seamlessly convert to/from Box…. This is not the case at the moment, AFAIK, however the API allows it, so the next release could definitely take advantage of this.


Old Answer

As you don't want to use Arc, you could use the new type pattern and wrap Rc inside a type that implements Send and Sync. These traits are unsafe to implement and after doing so it's all upto you to ensure that you don't cause undefined behaviour.


Wrapper around Rc would look like:

#[derive(Debug)]
struct RcWrapper<T> {
    rc: Rc<T>
}

impl<T> Deref for RcWrapper<T> {
    type Target = Rc<T>;

    fn deref(&self) -> &Self::Target {
        &self.rc
    }
}

unsafe impl<T: Send> Send for RcWrapper<T> {}
unsafe impl<T: Sync> Sync for RcWrapper<T> {}

Then,

#[derive(Debug)]
struct Transactions {
    list: Vec<RcWrapper<Transaction>>,
    index: HashMap<String, RcWrapper<Transaction>>,
}

Playground

Although, Deref trait is not very much worth in this case as most functions are associated. Generally Rc is cloned as Rc::clone() but you can still use the equivalentrc.clone() (probably the only case where Deref might be worth). For a workaround, you could have wrapper methods to call Rc's methods for clarity.

Update:

Found send_wrapper crate which seems to serve that purpose.

You could use it like:

use send_wrapper::SendWrapper;

#[derive(Debug)]
struct Transactions {
    list: Vec<SendWrapper<Rc<Transaction>>>,
    index: HashMap<String, SendWrapper<Rc<Transaction>>>,
}

PS: I would suggest to stick with Arc. The overhead is generally not that high unless you make alot of clones frequently. I am not sure how Rc is implemented. Send allows type to be sent into other threads and if there is anything thread-local, such as thread local locks or data, I am not sure how that would be handled.