Как я могу гарантировать, что тип, который не реализует синхронизацию, может быть безопасно разделен между потоками?


У меня есть код, который создает RefCell, а затем хочет передать ссылку на этот RefCell в один поток:

extern crate crossbeam;

use std::cell::RefCell;

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

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

В полном коде я использую тип, в который встроен RefCell (a typed_arena::Arena). Я использую поперечную балку , чтобы гарантировать, что поток не переживет ссылку, которую он принимает.

Это приводит к ошибке:

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

    scope.spawn(|| *val.borrow());
          ^~~~~

Я думаю, что понимаю, почему происходит эта ошибка: RefCell не предназначен для одновременного вызова из несколько потоков, и поскольку он использует внутреннюю изменяемость, нормальный механизм требования одного изменяемого заимствования не предотвратит несколько одновременных действий. Это даже задокументировано на Sync:

Типы

, которые не являются Sync,-это те, которые имеют "внутреннюю изменчивость" не потокобезопасным способом, такие как Cell и RefCell в std::cell.

Это все хорошо и хорошо, но в этом случае, я знаю, что только один поток может получить доступ к RefCell. Как я могу это сделать? подтвердите компилятору, что я понимаю, что я делаю, и я гарантирую, что это так? Конечно, если мои рассуждения о том, что это действительно безопасно, неверны, я буду более чем счастлив узнать, почему.

2 5

2 ответа:

Ну, одним из способов было бы использовать обертку с unsafe impl Sync:

extern crate crossbeam;

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());
    });
}
Таким образом, как обычно с unsafe, Теперь вы можете гарантировать, что внутренний RefCell действительно никогда не будет доступен из нескольких потоков одновременно. Насколько я понимаю, этого должно быть достаточно, чтобы не вызвать гонку данных.

Другое решение состоит в том, чтобы переместить изменяемую ссылку на элемент в поток, даже если изменяемость не требуется. Поскольку может быть только одна изменяемая ссылка, компилятор знает, что она безопасна для использования в другом потоке.

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