首页 > 解决方案 > 如何在线程之间传递带有 Rc 字段的结构(没有同时访问)

问题描述

我有以下(简化的)代码,它产生了一些线程,这些线程执行长而复杂的操作来构建Transactions结构。该Transactions结构包含带有Rc. 在线程结束时,我想Transactions通过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);
  }

}

编译器抱怨std::rc::Rc<Transaction>无法在线程之间安全发送,因为它没有实现std::marker::Send特征。

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 可以替换RcArc,但想知道是否有任何其他解决方案可以避免 using 的性能损失Arc,因为Rc两个线程永远不会同时访问这些结构。

标签: rust

解决方案


只是不要这样做。

我正在发表评论,但我认为它实际上回答了您的问题并警告其他人。

这似乎很不合理!Rc 不是关于管理访问,它是关于通过计算有多少引用是活动的来确保某些东西的寿命足够长,以便在不同的“所有者”/“借款人”之间共享。如果在两个不同的线程中有两个(Rc)对同一个值的引用,原子性的缺乏可能导致两个线程同时更改引用计数,这可能导致记录被弄脏,从而可能导致内存泄漏,或者更糟糕的是,过早地放弃分配和 UB。

这是因为递增共享变量的经典同步问题:

递增变量的步骤:

  1. 读取变量并将其存储在堆栈中。
  2. 将 1 添加到堆栈中的副本。
  3. 将结果写回变量中

一个线程就可以了,但让我们看看否则会发生什么:

多线程同步事件(线程 A 和 B)

  1. x=0
  2. A:将x读入xa(栈),xa = 0
  3. B:将x读入xb,xb =0
  4. A:增量xa,xa = 1
  5. A:将xa写入x,x=1
  6. B:增量 xb,xb = 1
  7. B:将 xb 写入 x,x = 1
  8. x 现在是 1

您现在已经将 0 增加了两次,结果为 1:BAD!

如果 x 是 Rc 的引用计数,它会认为只有一个引用是活动的。如果删除了一个引用,它会认为没有更多的引用活着并且会删除该值,但实际上仍然有一个引用认为可以访问数据,因此未定义的行为,因此非常糟糕!

与其他一切相比,Arc 的性能成本可以忽略不计,绝对值得使用。


推荐阅读