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