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