如何使用Arc在不同线程之间共享可变对象?

28

我正在尝试使用 Arc 在 Rust 的不同线程之间共享一个可变对象,但是我遇到了以下错误:

error[E0596]: cannot borrow data in a `&` reference as mutable
  --> src/main.rs:11:13
   |
11 |             shared_stats_clone.add_stats();
   |             ^^^^^^^^^^^^^^^^^^ cannot borrow as mutable

这是示例代码:

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

fn main() {
    let total_stats = Stats::new();
    let shared_stats = Arc::new(total_stats);

    let threads = 5;
    for _ in 0..threads {
        let mut shared_stats_clone = shared_stats.clone();
        thread::spawn(move || {
            shared_stats_clone.add_stats();
        });
    }
}

struct Stats {
    hello: u32,
}

impl Stats {
    pub fn new() -> Stats {
        Stats { hello: 0 }
    }

    pub fn add_stats(&mut self) {
        self.hello += 1;
    }
}

我能做些什么?

1个回答

34

Arc的文档说明:

Rust中的共享引用默认情况下不允许修改,而Arc也不例外:您通常无法获得对Arc内部内容的可变引用。如果需要通过Arc进行修改,请使用MutexRwLock或其中一个Atomic类型。

您可能需要将MutexArc结合使用:

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

struct Stats;

impl Stats {
    fn add_stats(&mut self, _other: &Stats) {}
}

fn main() {
    let shared_stats = Arc::new(Mutex::new(Stats));

    let threads = 5;
    for _ in 0..threads {
        let my_stats = shared_stats.clone();
        thread::spawn(move || {
            let mut shared = my_stats.lock().unwrap();
            shared.add_stats(&Stats);
        });
        // Note: Immediately joining, no multithreading happening!
        // THIS WAS A LIE, see below
    }
}

这主要是从 Mutex 文档中抄袭的。

我如何在 for 循环后使用 shared_stats?(我指的是 Stats 对象)。似乎 shared_stats 不能轻易地转换为 Stats。

从 Rust 1.15 开始,可以获取回值。另外,查看我的附加答案以获取另一种解决方案。

为什么在示例中有评论说没有多线程?

因为我混淆了! :-)

在示例代码中,thread::spawn 的结果(一个 JoinHandle)立即被丢弃,因为它没有存储在任何地方。当处理被丢弃时,该线程被分离,可能永远不会完成。我将其与 JoinGuard 混淆,这是一个旧的、已删除的 API,在被丢弃时加入。对于造成的困惑,我很抱歉!


在编辑时,我建议完全避免可变性:

use std::{ops::Add, thread};

#[derive(Debug)]
struct Stats(u64);

// Implement addition on our type
impl Add for Stats {
    type Output = Stats;
    fn add(self, other: Stats) -> Stats {
        Stats(self.0 + other.0)
    }
}

fn main() {
    let threads = 5;

    // Start threads to do computation
    let threads: Vec<_> = (0..threads).map(|_| thread::spawn(|| Stats(4))).collect();

    // Join all the threads, fail if any of them failed
    let result: Result<Vec<_>, _> = threads.into_iter().map(|t| t.join()).collect();
    let result = result.unwrap();

    // Add up all the results
    let sum = result.into_iter().fold(Stats(0), |i, sum| sum + i);
    println!("{:?}", sum);
}

在这里,我们保留对JoinHandle的引用,然后等待所有线程完成。然后我们收集结果并将它们全部相加。这是常见的map-reduce模式。请注意,没有线程需要任何可变性,所有操作都在主线程中完成。

它似乎完美地工作了!但是有两个问题,我如何在for之后使用shared_stats?(我说的是Stats对象)。似乎shared_stats不能轻松转换为Stats。此外,它说没有多线程。为什么? - Razican
一个问题是:Arc如何实现以确保这种行为?我对此感到惊讶,我相信这属于标准库而不是核心语言特性。 - hqt
@hqt Arc源代码是可用且易读的。其中有一个原子整数,在克隆时递增,在释放时递减。当它归零时,数据本身就会被释放。 - Shepmaster

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接