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

标签 multithreading concurrency rust reference-counting

我正在尝试使用 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;
    }
}

我能做什么?

最佳答案

Arc的文档说:

Shared references in Rust disallow mutation by default, and Arc is no exception: you cannot generally obtain a mutable reference to something inside an Arc. If you need to mutate through an Arc, use Mutex, RwLock, or one of the Atomic types.

您可能需要一个 Mutex 与一个 Arc 组合:

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 文档中抄袭的。

How can I use shared_stats after the for? (I'm talking about the Stats object). It seems that the shared_stats cannot be easily converted to Stats.

从 Rust 1.15 开始,it's possible to get the value back .另请参阅我的其他解决方案的附加答案。

[A comment in the example] says that there is no multithreading. Why?

因为我糊涂了! :-)

在示例代码中,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 模式。请注意,没有线程需要任何可变性,这一切都发生在主线程中。

关于multithreading - 如何使用 Arc 在线程之间共享可变对象?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31373255/

相关文章:

rust - 从切片生成的迭代器使用什么类型签名?

rust - 查找泛型类型 T 的最大允许值

rust - 使用 wasm-pack 构建时在 Rust 和 JavaScript 之间传递字符串

javascript - 事件驱动编程node.js?

java - setContextClassLoader 的含义

java - 使用该关键字作为并发锁

C#并发列表问题

java - 使用 request.getSession() 作为锁定对象?

javascript - 如何在nodejs中的单独线程上执行dos命令?

c++ - 为什么允许编译器优化这个繁忙的等待循环?