【问题标题】:How do I share a mutable object between threads using Arc?如何使用 Arc 在线程之间共享可变对象?
【发布时间】:2015-10-01 02:53: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;
    }
}

我能做什么?

【问题讨论】:

    标签: multithreading concurrency rust reference-counting


    【解决方案1】:

    Arc 的文档说:

    默认情况下,Rust 中的共享引用不允许突变,Arc 也不例外:您通常无法获得对 Arc 中某些内容的可变引用。如果您需要通过Arc 进行变异,请使用MutexRwLockAtomic 类型之一。

    您可能希望将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 开始,it's possible to get the value back。另请参阅我的其他解决方案。

    [示例中的注释] 说没有多线程。为什么?

    因为我糊涂了! :-)

    在示例代码中,thread::spawnJoinHandle)的结果立即被删除,因为它没有存储在任何地方。当句柄被放下时,线程分离并且可能永远也可能永远不会完成。我将它与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。此外,它说没有多线程。为什么?
    • 一个问题是:Arc 如何实现这种行为?我对它感到惊讶,我相信它属于标准库而不是核心语言特性。
    • @hqt source code for Arc 可用且可读。有一个原子整数,在克隆时递增,在删除时递减。当它达到零时,数据本身就会被丢弃。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-08-08
    • 2021-01-27
    • 1970-01-01
    • 2019-05-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多