如何保证在线程之间实际可以安全地共享未实现Sync的类型?

She*_*ter 6 unsafe thread-safety rust

我有代码创建一个RefCell然后想要将它的引用传递RefCell给单个线程:

extern crate crossbeam;

use std::cell::RefCell;

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

    crossbeam::scope(|scope| {
        scope.spawn(|| *val.borrow());
    });
}
Run Code Online (Sandbox Code Playgroud)

在完整的代码中,我使用的是一种RefCell嵌入其中的类型(a typed_arena::Arena).我正在使用crossbeam来确保线程不会超过它所引用的引用.

这会产生错误:

error: the trait bound `std::cell::RefCell<i32>: std::marker::Sync` is not satisfied [E0277]

    scope.spawn(|| *val.borrow());
          ^~~~~
Run Code Online (Sandbox Code Playgroud)

我相信我理解为什么会发生这样的错误:RefCell不是设计为从多个线程同时调用,并且因为它使用内部可变性,所以需要单个可变借位的正常机制不会阻止多个并发操作.这甚至记录在Sync:

不属于类型Sync是那些具有"室内易变性"在非线程安全的方式,比如Cell和RefCell中std::cell.

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

She*_*ter 8

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

extern crate crossbeam;

use std::cell::RefCell;

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

    crossbeam::scope(|scope| {
        scope.spawn(move || *val2.borrow());
    });
}
Run Code Online (Sandbox Code Playgroud)

  • 这是允许的,因为`RefCell <i32>`实现`发送'. (3认同)

Vla*_*eev 5

一种方法是使用带有以下内容的包装器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();
}
Run Code Online (Sandbox Code Playgroud)

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