【问题标题】:Can I safely multithread something which isn't meant to be multithreaded?我可以安全地对不应该是多线程的东西进行多线程处理吗?
【发布时间】:2018-10-18 03:11:20
【问题描述】:

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

现在,虽然它使用多线程,但它会在互斥体后面,所以它不能同时在两个线程上使用。

生锈是为了保护我免受什么侵害,我能做些什么吗?

示例参考,我的示例代码是:

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)`

【问题讨论】:

  • 好吧,由于其他原因(siv 主循环永远不会返回锁),它不会工作,但主要问题仍然存在。
  • 请包含更多代码以查看。对您编写的类型的天真假设有效。见play.rust-lang.org/… 基本上,你的错误信息说Cursive 类型不是Sync,所以Arc<Mutex<Cursive>> 不是Send
  • @EarthEngine 为什么 Mutex 不能让它同步?这不是 Mutex 的重点吗?
  • 这就是为什么我需要看看事物是如何定义的......猜测没有意义。
  • @EarthEngine 这够了吗?

标签: multithreading rust mutex


【解决方案1】:

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

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

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

你没有提供足够的上下文来肯定地说,但可能不是。

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

为什么 Mutex 不让它同步?这不是互斥锁的意义吗?

没有。当它还不是线程安全的时,它不能使某些东西成为线程安全的。 Mutex 只是管理对一个值的独占访问,它不会使来自不同线程的访问安全。唯一可以使类型成为线程安全的就是所讨论的类型。

猜测:这个库被编写成不需要线程安全,因此Arc 不能假设它是线程安全的,所以它拒绝编译。

【讨论】:

    【解决方案2】:

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

    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&lt;Mutex&lt;T&gt;&gt; 必须是Send,因为您将这样的东西发送到另一个线程;因此:
    • Mutex&lt;T&gt; 必须是 SendSync,因为 Arc 要求引用计数对象是 SendSync。因此:
    • T 必须是 Send,因为同一个对象将在不同的线程中被访问而没有任何进一步的保护。

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

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

    你可以自己试试!

    Mutex&lt;T&gt;: Send + Sync 需要 T: Send

    要了解原因,首先要问一个问题:什么不能是Send

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

    现在假设你有一个Mutex&lt;&amp;Cell&lt;T&gt;&gt;,因为受保护的东西只是一个引用,而不是Cell 本身,Cell 本身可能仍然在某个地方不受保护。因此,当您调用 lock().set() 时,编译器无法得出结论,没有导致数据竞争的风险。所以编译器会阻止它Send

    如果我不得不...

    所以我们看到&amp;Cell&lt;T&gt; 不是Send,所以即使它在Mutex 中受到保护,我们仍然不能在另一个线程中使用它。那我们能做什么呢?

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

    如果在其他语言(.NET、Java...)中不这样做,最好的情况就是抛出异常,最坏的情况是导致未定义的行为。再一次,Rust 将这种违规转化为编译错误,无需特殊处理(&amp;Cell&lt;T&gt; 与 UI 无关),这真是太好了!

    所以,如果这是您想要做的,您必须做同样的事情:仅在 UI 线程中访问视图对象。具体如何操作取决于您使用的 API。

    【讨论】:

      猜你喜欢
      • 2012-08-09
      • 1970-01-01
      • 1970-01-01
      • 2018-01-01
      • 1970-01-01
      • 2011-02-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多