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

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

extern crate crossbeam;

use std::cell::RefCell;

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

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

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

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

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 ответа

Решение

Ну, одним из способов было бы использовать обертку с 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());
    });
}
Другие вопросы по тегам