如何在线程之间共享可变对象?

时间:2015-07-12 22:35:18

标签: multithreading concurrency rust

我尝试使用Arc在Rust中的线程之间共享一个可变对象,但是我收到了这个错误:

error: cannot borrow immutable borrowed content as mutable
  --> src/main.rs:13:13
   |
13 |             shared_stats_clone.add_stats(&stats);
   |             ^^^^^^^^^^^^^^^^^^

这是示例代码:

use std::thread;
use std::sync::Arc;

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

    let threads = 5;
    for t in 0..threads {
        let mut shared_stats_clone = shared_stats.clone();
        thread::spawn(move || {
            let mut stats = Stats::new();
            shared_stats_clone.add_stats(&stats);
        });
    }
}

struct Stats {
    hello: u32,
}

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

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

我该怎么办?

1 个答案:

答案 0 :(得分:18)

Arc将自己描述为:

  

用于共享状态的原子引用计数包装器。

请注意,它是共享状态,而不是共享可变状态。对于共享可变状态,您可能希望MutexArc结合使用:

use std::sync::{Arc, Mutex};
use std::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。

这是正确的,一旦你在ArcMutex中放置了某些内容,就无法将对象取回。你永远不会知道还有什么引用共享状态,所以你永远不知道是否可以安全地取回所有权。

你可以锁定它并获得一个可变或不可变的引用,就是它。

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

  

它说没有多线程。为什么呢?

因为我感到困惑! : - )

在示例代码中,thread::spawn(a JoinHandle)的结果会立即被删除,因为它没有存储在任何地方。当手柄掉落时,线程分离并且可能会或可能不会完成。我把它与JoinGuard混淆了,{{3}}是一个不稳定的API,当它被删除时加入。抱歉混乱!

对于一些社论,我可能建议完全避免可变性:

use std::ops::Add;
use std::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 模式。请注意,没有线程需要任何可变性,它都发生在主线程中。