首页 > 解决方案 > 如果创建可变变量和可变引用并在单独的线程中更改其中之一,Rust 会发生什么?

问题描述

我是 Rust 的新手,刚刚开始接触它的所有权系统(本书的第 4 章)。编译器处理了很多值和引用可能发生的错误,但让我们想象一下下面的情况:

假设有这样的代码,但会change_string打开一个新线程并在那里执行突变。

fn main() {
    let mut s = String::from("Hello");

    // perform in thread, so flow will not be synchronous
    change_string(&mut s);

    s.push_str("..........");
    println!("{}", s);
}

fn change_string(some_string: &mut String) {
    some_string.push_str(", World");
}

目前我收到Hello, World..........了,但是如果添加, World将在单独的线程中会发生什么?

标签: concurrencyrustownership

解决方案


Rust 不会让你做不安全的事情(不使用unsafe关键字),包括与线程相关的任何事情。为什么不试试看它是如何编译失败的呢?

fn change_string(some_string: &mut String) {
    std::thread::spawn(move || {
            some_string.push_str(", World");
    });
}

它会产生此错误:

error[E0621]: explicit lifetime required in the type of `some_string`
  --> src/main.rs:14:5
   |
13 | fn change_string_1(some_string: &mut String) {
   |                                 ----------- help: add explicit lifetime `'static` to the type of `some_string`: `&'static mut std::string::String`
14 |     std::thread::spawn(move || {
   |     ^^^^^^^^^^^^^^^^^^ lifetime `'static` required

这意味着您不能将具有非静态生命周期的引用传递给线程,因为线程的生命周期可能比此类引用更长,这是不安全的。

您可能正在考虑数据竞争的问题,但这些实际上不是问题。真正的问题是,您String可能与线程一样长。实际上,如果您使用来自crossbeamcrate 的作用域线程,它将正常工作:

fn change_string(some_string: &mut String) {
    crossbeam::scope(|s| {
        s.spawn(|_| {
            some_string.push_str(", World");
        });
    }).unwrap();
}

现在它起作用了,因为在crossbeam::scope()内部产生的所有线程都完成之前,调用不会返回。因此,您的生命周期String总是比线程长,并且一切正常。

但是数据竞赛呢?没有,因为&mutRust 中的引用是唯一的。这意味着您不能让它们中的两个指向同一个对象,因此从哪个线程更改对象并不重要(只要您的对象实现Sync但大多数都这样做),您只从一个线程执行它,并且没有种族。

如果您尝试创建两个线程来修改对象,即使使用crossbeam

fn change_string(some_string: &mut String) {
    crossbeam::scope(|s| {
        s.spawn(|_| {
            some_string.push_str(", World");
        });
        s.spawn(|_| {
            some_string.push_str(", World");
        });
    }).unwrap();
}

您收到此错误消息:

error[E0524]: two closures require unique access to `some_string` at the same time

请记住,这&mut意味着除了变异能力之外的独特访问权限。

如果您尝试将引用移动到闭包中而不是借用它:

fn change_string(some_string: &mut String) {
    crossbeam::scope(|s| {
        s.spawn(move |_| {
            some_string.push_str(", World");
        });
        s.spawn(move |_| {
            some_string.push_str(", World");
        });
    }).unwrap();
}

你在第二个闭包中得到这个:

error[E0382]: use of moved value: `some_string`

因为该引用已移至第一个引用,无法再次使用。

当然,您可以从线程中生成一个线程,这将起作用:

fn change_string(some_string: &mut String) {
    crossbeam::scope(|s| {
        s.spawn(|s| {
            some_string.push_str(", World");
            s.spawn(|_| {
                some_string.push_str(", World");
            });
        });
    }).unwrap();
}

但是请注意它仍然是完全无竞争的,因为一旦创建了第二个线程,第一个线程就失去了对唯一可变引用的访问权限。


推荐阅读