【问题标题】:How can I guarantee that a type that doesn't implement Sync can actually be safely shared between threads?如何保证不实现 Sync 的类型实际上可以在线程之间安全共享?
【发布时间】:2016-04-15 14:27:07
【问题描述】:

我的代码创建了一个RefCell,然后想将对该RefCell 的引用传递给一个单个线程:

use crossbeam; // 0.7.3
use std::cell::RefCell;

fn main() {
    let val = RefCell::new(1);

    crossbeam::scope(|scope| {
        scope.spawn(|_| *val.borrow());
    })
    .unwrap();
}

在完整的代码中,我使用了一个嵌入了RefCell 的类型(typed_arena::Arena)。我使用crossbeam 来确保线程不会超过它所使用的引用。

这会产生错误:

error[E0277]: `std::cell::RefCell<i32>` cannot be shared between threads safely
 --> src/main.rs:8:15
  |
8 |         scope.spawn(|_| *val.borrow());
  |               ^^^^^ `std::cell::RefCell<i32>` cannot be shared between threads safely
  |
  = help: the trait `std::marker::Sync` is not implemented for `std::cell::RefCell<i32>`
  = note: required because of the requirements on the impl of `std::marker::Send` for `&std::cell::RefCell<i32>`
  = note: required because it appears within the type `[closure@src/main.rs:8:21: 8:38 val:&std::cell::RefCell<i32>]`

我相信我理解为什么会发生此错误:RefCell 并非旨在从多个线程同时调用,并且由于它使用内部可变性,因此要求单个可变借用的正常机制不会阻止多个并发操作。这甚至记录在Sync

不是Sync 的类型是那些以非线程安全形式具有“内部可变性”的类型,例如cell::Cellcell::RefCell

这一切都很好,但是在这种情况下,我知道只有一个线程能够访问RefCell。我如何向编译器确​​认我理解我在做什么并确保是这种情况?当然,如果我认为这实际上是安全的推理不正确,我很乐意被告知原因。

【问题讨论】:

    标签: thread-safety rust unsafe


    【解决方案1】:

    这种情况的另一个解决方案是将对该项目的可变引用移动到线程中,即使不需要可变性。由于只能有一个可变引用,因此编译器知道在另一个线程中使用它是安全的。

    use crossbeam; // 0.7.3
    use std::cell::RefCell;
    
    fn main() {
        let mut val = RefCell::new(1);
        let val2 = &mut val;
    
        crossbeam::scope(|scope| {
            scope.spawn(move |_| *val2.borrow());
        })
        .unwrap();
    }
    

    作为bluss points out:

    这是允许的,因为RefCell&lt;i32&gt; 实现了Send

    【讨论】:

    • 这是允许的,因为RefCell&lt;i32&gt; 实现了Send
    【解决方案2】:

    一种方法是使用带有unsafe impl Sync 的包装器:

    use crossbeam; // 0.7.3
    use std::cell::RefCell;
    
    fn main() {
        struct Wrap(RefCell<i32>);
        unsafe impl Sync for Wrap {};
        let val = Wrap(RefCell::new(1));
    
        crossbeam::scope(|scope| {
            scope.spawn(|_| *val.0.borrow());
        })
        .unwrap();
    }
    

    unsafe 一样,现在由您来保证内部RefCell 确实不会同时从多个线程访问。据我了解,这应该足以不引起数据竞争。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-02-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-08
      相关资源
      最近更新 更多