我可以安全地对不是多线程的对象进行多线程吗?

时间:2018-10-18 03:11:20

标签: multithreading rust mutex

我使用的特征不是围绕多线程(草书)设计的。

现在,当它使用多线程时,它将位于互斥锁的后面,因此它将无法同时在两个线程中使用。

什么是铁锈试图保护我免受伤害,我能对此采取任何措施吗?

作为示例参考,我的示例代码为:

extern crate cursive;

use cursive::Cursive;
use std::thread;
use std::sync::{Mutex,Arc};

fn main() {
    let mut siv = Arc::new(Mutex::new(Cursive::default()));
    let copy_siv = siv.clone();

    thread::spawn(move || {
        let mut new_siv = copy_siv.lock().unwrap();
    });

    (*(siv.lock().unwrap())).run();
 }

编译器抱怨thread::spawn

   Error[E0277]: `(dyn cursive::traits::View + 'static)` cannot be sent between threads safely
   --> src/main.rs:16:5
   |
16 |     thread::spawn(move || {
   |     ^^^^^^^^^^^^^ `(dyn cursive::traits::View + 'static)` cannot be sent between threads safely
   |
   = help: the trait `std::marker::Send` is not implemented for `(dyn cursive::traits::View + 'static)`

2 个答案:

答案 0 :(得分:3)

  

什么是铁锈试图保护我免受[...]

线程之间要发送的内容中包含dyn cursive::traits::View特征对象。此特征对象不是Send。它必须为Send,因为通过将其放入Arc中,您将无法再预测哪个线程将负责销毁它,因此在线程之间转移所有权必须是安全的。

  

[...]我能做些什么吗?

您尚未提供足够的上下文可以肯定地说,但可能没有。

您可以也许尝试使用普通借用的引用(加上支持作用域线程的线程库),但我不能说这是否对您有用。

  

为什么Mutex不能使其同步?这不是Mutex的重点吗?

不。当它还不是线程安全的时,它就不能使线程安全。 Mutex仅管理对值的独占访问,并不能保证从不同线程进行的访问都是安全的。唯一可以使类型成为线程安全的是相关类型。

猜测一下:库的编写不需要任何线程安全性,因此Arc无法假定它是线程安全的,因此它拒绝编译。

答案 1 :(得分:0)

我不知道您的实际代码是什么。但是以下示例复制了您遇到的确切错误:

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

struct Cursive;
impl Default for Cursive {
    fn default() -> Self {
        Cursive
    }
}
trait View{
    fn run(&self);
}
impl View for Cursive{
    fn run(&self){}
}

fn main() {
    let mut siv:Arc<Mutex<dyn View>> = Arc::new(Mutex::new(Cursive::default()));
    let copy_siv = siv.clone();

    thread::spawn(move || {
        let mut new_siv = copy_siv.lock().unwrap();
    });

    (*(siv.lock().unwrap())).run();
}

您可以在playground中进行尝试。错误消息:

error[E0277]: `dyn View` cannot be sent between threads safely
  --> src/main.rs:21:5
   |
21 |     thread::spawn(move || {
   |     ^^^^^^^^^^^^^ `dyn View` cannot be sent between threads safely
   |
   = help: the trait `std::marker::Send` is not implemented for `dyn View`
   = note: required because of the requirements on the impl of `std::marker::Send` for `std::sync::Mutex<dyn View>`
   = note: required because of the requirements on the impl of `std::marker::Send` for `std::sync::Arc<std::sync::Mutex<dyn View>>`
   = note: required because it appears within the type `[closure@src/main.rs:21:19: 23:6 copy_siv:std::sync::Arc<std::sync::Mutex<dyn View>>]`
   = note: required by `std::thread::spawn`

分析和解决方案

该错误消息向有经验的用户解释了所有内容。对于那些不熟悉该语言的人,siv是一个引用计数,互斥保护的特征对象。该对象仅被称为View,编译器没有证据表明它是否为Send。但是,要使代码正常工作,

  • Arc<Mutex<T>>必须为Send,因为您正在将这样的事情发送到另一个线程;因此:
  • Mutex<T>必须为SendSync,因为Arc要求引用计数的对象为SendSync。因此:
  • T必须为Send,因为相同的对象将在不同的线程中访问而没有任何进一步的保护。

因此,此代码不起作用。解决方法是

let mut siv:Arc<Mutex<dyn View + Send>> = ...

您可以自己尝试!

Mutex<T>: Send + Sync要求T: Send

要了解原因,请先提出一个问题:Send不能是什么?

一个例子是,对具有内部可变性的事物的引用不能为Send。因为如果是这样,人们可以通过内部线程在不同线程中对事物进行变异,从而导致数据争用。

现在假设您有一个Mutex<&Cell<T>>,因为受保护的东西仅是引用,而不是Cell本身,因此Cell本身仍然可能不受保护。因此,当您调用lock().set()时,编译器无法得出结论,不会引起数据争用。因此,编译器阻止它从Send开始。

如果我不得不...

因此我们看到&Cell<T>不是Send,因此即使它在Mutex中受到保护,我们仍然不能在其他线程中使用它。那我们该怎么办?

这个问题实际上并不新鲜。几乎所有UI API都有相同的问题:UI组件是在UI线程中创建的,因此您无法在任何其他线程中访问它们。相反,您必须安排要在UI线程中运行的例程,并让UI线程访问组件。

在其他语言(.NET,Java ...)中未这样做的情况将以最佳方式抛出异常,从而在最严重的情况下导致未定义的行为。再一次,Rust将这种违规行为变成没有特殊处理的编译错误(&Cell<T>与UI无关),这真的很好!

因此,如果这是您要执行的操作,则必须执行相同的操作:仅在UI线程中访问视图对象。具体操作取决于您使用的API。