Concurrency 如果创建可变变量和可变引用并在单独的线程中更改其中一个,Rust会发生什么?

Concurrency 如果创建可变变量和可变引用并在单独的线程中更改其中一个,Rust会发生什么?,concurrency,rust,ownership,Concurrency,Rust,Ownership,我在《铁锈》一书中是一个完全的新手,刚刚开始进入它的所有权体系,这本书的第四章。编译器处理许多可能发生在值和引用中的错误,但让我们想象一下以下情况: 让我们假设有这样的代码,但change_string会打开一个新线程并在那里执行突变 fn main() { let mut s = String::from("Hello"); // perform in thread, so flow will not be synchronous change_string(&

我在《铁锈》一书中是一个完全的新手,刚刚开始进入它的所有权体系,这本书的第四章。编译器处理许多可能发生在值和引用中的错误,但让我们想象一下以下情况:

让我们假设有这样的代码,但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将在单独的线程中会发生什么?

如果不使用不安全关键字,Rust将不允许您执行不安全的操作,其中包括与线程相关的任何操作。为什么不试试看它是如何编译失败的呢

fn change_string(some_string: &mut String) {
    std::thread::spawn(move || {
            some_string.push_str(", World");
    });
}
它会产生以下错误:

错误[E0621]:在'some_字符串类型中需要显式生存期` ->src/main.rs:14:5 | 13 | fn更改字符串_1某些字符串:&mut字符串{ |----帮助:将显式生存期“static”添加到“some_string”类型:“&”static mut std::string::string` 14 | std::thread::spawnmove |{ |^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^需要终身“静态” 这意味着您不能将具有非静态生存期的引用传递给线程,因为该线程的生存期可能比此类引用长,这将是不安全的

您可能在考虑数据争用的问题,但事实并非如此。真正的问题是字符串的寿命可能与线程的寿命一样长。事实上,如果您使用横梁板条箱中的作用域线程,它只会工作:

fn change_string(some_string: &mut String) {
    crossbeam::scope(|s| {
        s.spawn(|_| {
            some_string.push_str(", World");
        });
    }).unwrap();
}
现在它可以工作了,因为在内部生成的所有线程完成之前,crossbeam::scope调用不会返回。因此,字符串的生存期总是严格地比线程长,并且一切正常

但是数据争用又如何呢?没有,因为Rust中的&mut引用是唯一的。这意味着您不能让它们中的两个指向同一个对象,所以从哪个线程更改对象并不重要,只要您的对象实现了同步,但大多数都是从一个线程执行的,并且没有争用

如果尝试创建两个线程来修改对象,即使使用横梁:

您将收到以下错误消息:

错误[E0524]:两个闭包需要同时对'some_string'进行唯一访问 记住&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();
}
在第二个闭包中得到了这个:

错误[E0382]:使用移动值:`some_字符串` 因为引用已移动到第一个引用中,无法再次使用

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

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();
}

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

如果不使用不安全关键字,Rust将不允许您执行不安全的操作,该关键字包括与线程相关的任何内容。为什么不试试看它是如何编译失败的呢

fn change_string(some_string: &mut String) {
    std::thread::spawn(move || {
            some_string.push_str(", World");
    });
}
它会产生以下错误:

错误[E0621]:在'some_字符串类型中需要显式生存期` ->src/main.rs:14:5 | 13 | fn更改字符串_1某些字符串:&mut字符串{ |----帮助:将显式生存期“static”添加到“some_string”类型:“&”static mut std::string::string` 14 | std::thread::spawnmove |{ |^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^需要终身“静态” 这意味着您不能将具有非静态生存期的引用传递给线程,因为该线程的生存期可能比此类引用长,这将是不安全的

您可能在考虑数据争用的问题,但事实并非如此。真正的问题是字符串的寿命可能与线程的寿命一样长。事实上,如果您使用横梁板条箱中的作用域线程,它只会工作:

fn change_string(some_string: &mut String) {
    crossbeam::scope(|s| {
        s.spawn(|_| {
            some_string.push_str(", World");
        });
    }).unwrap();
}
现在它可以工作了,因为在内部生成的所有线程完成之前,crossbeam::scope调用不会返回。因此,字符串的生存期总是严格地比线程长,并且一切正常

但是数据争用又如何呢?没有,因为Rust中的&mut引用是唯一的。这意味着您不能让它们中的两个指向同一个对象,所以从哪个线程更改对象并不重要,只要您的对象实现了同步,但大多数都是从一个线程执行的,并且没有争用

如果尝试创建两个线程来修改对象,即使使用横梁:

您将收到以下错误消息:

错误[E0524]:两个闭包需要同时对'some_string'进行唯一访问 记住&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();
}
在第二个闭包中得到了这个:

错误[E0382]:使用移动的值 :`一些` 因为引用已移动到第一个引用中,无法再次使用

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

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();
}

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

您要问的具体代码是什么?rodrigo的回答非常透彻地解释了为什么这不太可能是一个问题。这个问题没有答案,因为1编译器不允许您这样做,因为&mut不能发送到其他线程以防止第二个原因2结果未定义。如果没有某种程度上定义操作顺序的原子,那么各种事情都可能发生。例如,更改字符串线程可能根本没有被操作系统调度,而您得到Hello……,然后第二个线程运行,字符串已经被释放,欢闹接踵而至……您询问的具体代码是什么?rodrigo的回答非常透彻地解释了为什么这不太可能是一个问题。这个问题没有答案,因为1编译器不允许您这样做,因为&mut不能发送到其他线程以防止第二个原因2结果未定义。如果没有某种程度上定义操作顺序的原子,那么各种事情都可能发生。例如,操作系统可能根本没有安排更改字符串线程,而您得到了Hello……,然后第二个线程运行,字符串已被释放,欢闹接踵而至……非常感谢!。我试过第一个例子中的线程,但不确定是否有一些方法可以绕过it@BLukash:除了横梁板条箱,您可以使用互斥锁。与其他语言不同,Rust中的互斥不能被滥用。非常感谢!。我试过第一个例子中的线程,但不确定是否有一些方法可以绕过it@BLukash:除了横梁板条箱,您可以使用互斥锁。与其他语言不同,Rust中的互斥不能被滥用。