在Rust中共享可变变量的线程间通信

14

编辑注:此代码示例来自 Rust 1.0 之前的版本,不是语法上有效的 Rust 1.0 代码。

在 Rust 中,是否可以在多个线程之间共享可变变量?考虑以下情况:

fn main() {

    let mut msg = "Hi";
    // ...
    msg = "Hello, World!";

    do spawn {
        println(msg);
    }

    do spawn {
        println(msg);
    }

}

我遇到了这个错误:

error

变量只需要对生成的线程是只读的。但是变量必须是可变的,因为我想要做的实际上是在多个线程之间共享一个 HashMap。据我所知,除非它是可变的,否则没有办法填充 HashMap。即使有一种方法可以做到这一点,我仍然对如何通常实现此类事情感兴趣。

谢谢!

4个回答

9

这个限制预计会在将来的版本中取消。话说,你可以在第一个 do spawn 之前加上 let msg = msg; 来解决这个问题。这将把值 msg 移动到一个不可变位置,从而有效地改变其可变性。


2
当你说“这个限制将被取消”时,具体是什么在改变?复制捕获将成为默认值吗?或者,只要你的 lambda 在最后一次分配之后创建,就可以捕获可变变量? - Jason Orendorff

6
“一般而言”(关于在共享哈希表时保持其可变性)的答案是不直接可能的;Rust旨在禁止共享可变状态,以避免某些类型的错误并保护某些类型的安全(以及出于其他原因)。但是,可以创建类似于共享可变哈希表的东西。想象一下,单个任务有一个可变的哈希表。您可以编写代码,使其他任务可以发送消息,说实际上“更新哈希表,使5映射到18”,或者“告诉我7映射到什么”。这种间接操作的目的是什么?首先,通过将值复制到消息中,另一个任务无法践踏您正在阅读的内存(一个重要的安全问题),其次,通过间接编写代码,程序员更清楚什么是原子操作和什么不是;程序员将知道不要期望关于哈希表内容的两条连续消息一定是关于相同版本的哈希表,这种假设在未共享、可变哈希表的两次连续读取中是完全有效的(至少在Rust中,感谢其他限制)。

这肯定听起来是一个有前途的方法。标准库中是否有任何特定的东西可以帮助我完成这样的事情? - Evan Byrne
不是我知道的,抱歉!标准库仍在增长中;您可能需要考虑自己添加它。 - Paul Stansifer
如果我找到了一种方法,我一定会在Github上分享它 :) - Evan Byrne

3
如果您的变量可以是原子性的,您可以使用 Arc 进行引用计数和原子类型。 Arc 是一个类似于具有垃圾回收功能的语言中的垃圾收集器的引用计数器:最初的回答。
use std::sync::atomic;
use std::sync::Arc;

fn main() {
    let arc_num = Arc::new(atomic::AtomicU32::new(1337));
    let arc_num_clone = Arc::clone(&arc_num);

    let _get_borrowed_clone = move || {
        return arc_num_clone.load(atomic::Ordering::Relaxed);
    };

    arc_num.store(4242, atomic::Ordering::Relaxed)
}

最初的回答:
很可能你不能使用原子类型,这种情况下你可以使用Arc和Mutex:

playground

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

fn main() {
    let arc_num = Arc::new(Mutex::new(1337));
    let arc_num_clone = Arc::clone(&arc_num);

    let _get_borrowed_clone = move || {
        let out = *arc_num_clone.lock().unwrap();
        return out;
    };

    *arc_num.lock().unwrap() = 4242;
}

阅读更多关于此代码的工作原理的信息

如果您想了解这两种方法之间的区别,请阅读“mutex”和“atomic operation”之间有什么区别吗?

(Original Answer翻译成“最初的回答”)

2
在共享字符串 "Hello, World!" 的情况下,您只需要将变量移回到不可变位置(例如,let mut msg = .....; let msg = msg;)。
可能您还想避免为每个接收它的线程制作单独的哈希映射副本,在这种情况下,您还需要将其放在 std::sync::Arc 中(原子引用计数包装器)。

所以当执行 let msg = msg; 时,Rust 会为每个新线程创建一个不可变的 msg 副本? - Evan Byrne
在这种情况下,Rust使用新的不可变引用来遮蔽先前的可变引用。然后可以将此不可变引用传递给线程。至少这是我理解这种行为的方式。 - Rauni Lillemets

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