#rust
Вопрос:
Я использую ящик downcast_rs для передачи вниз с Arc<Mutex<dyn MyTrait>>
на Arc<Mutex<MyConcreteObject>>
struct SafeMyStruct(Arc<Mutex<MyStruct>>);
unsafe impl Send for SafeMyStruct {}
unsafe impl Sync for SafeMyStruct {}
таким образом, компилятор знает, что это безопасно, потому что мы будем обращаться к этим указателям только по одному за раз, потому что они защищены a Mutex
.
Возможно ли это в downcast_rs
? Вот что я попробовал:
use downcast_rs::{DowncastSync, impl_downcast};
use std::sync::{Arc, Mutex};
trait Base: DowncastSync {}
impl_downcast!(sync Base);
#[derive(Debug)]
struct Foo {
x: *mut u8
}
//Make Arc<Mutex<Foo>>: Send Sync
struct SafeFoo(Arc<Mutex<Foo>>);
unsafe impl Send for SafeFoo {}
unsafe impl Sync for SafeFoo {}
impl Base for Foo {}
fn main() {
let mut x = 0;
let base: Arc<Mutex<dyn Base>> = Arc::new(Mutex::new(Foo{x: amp;mut x}));
let base_ = base.clone();
std::thread::spawn(move ||{
base_;
});
}
Ошибка:
error[E0277]: `*mut u8` cannot be shared between threads safely
--> src/main.rs:16:6
|
4 | trait Base: DowncastSync {}
| ------------ required by this bound in `Base`
...
16 | impl Base for Foo {}
| ^^^^ `*mut u8` cannot be shared between threads safely
|
= help: within `Foo`, the trait `Sync` is not implemented for `*mut u8`
= note: required because it appears within the type `Foo`
error[E0277]: `*mut u8` cannot be sent between threads safely
--> src/main.rs:16:6
|
4 | trait Base: DowncastSync {}
| ------------ required by this bound in `Base`
...
16 | impl Base for Foo {}
| ^^^^ `*mut u8` cannot be sent between threads safely
|
= help: within `Foo`, the trait `Send` is not implemented for `*mut u8`
= note: required because it appears within the type `Foo`
error: aborting due to 2 previous errors; 1 warning emitted
Несмотря Arc<Mutex<Foo>>
на то Send Sync
, что это так , похоже, что это вынуждает Foo
быть Send
Sync
, что привело бы к неопределенному поведению, если бы мы реализовали.
Есть ли способ это исправить?
Комментарии:
1. Если
Foo
есть!Send
, тоArc<Mutex<Foo>>
есть!Send
. Добавлениеunsafe impl Send for SafeFoo
выглядит как плохая перевязка к проблеме. Вместо этого вам следуетSend
включитьFoo
(иSync
при необходимости). Я не знаю , решает ли это проблемуdowncast_rs
, но, тем не менее, это было бы более идиоматично.2. @kmdreko извините, я не имел
!Send !Sync
в виду, я просто не имел в виду ниSend
то, ниSync
другое, например, когда у него есть указатель. Как я могу заставитьArc<Mutex<MyNonSendSyncStruct>>
бытьSend Sync
?3. Вам нужна структура , чтобы быть
Send
, это единственный способ. Это не обязательно нужноSync
, такMutex<T>
как естьSync
, еслиT
естьSend
.4. «что привело бы к неопределенному поведению, если бы мы реализовали» — если реализация
Send
forFoo
приведет к неопределенному поведению, то включение этогоArc<Mutex<Foo>>
не решит проблему.Send
это просто маркер, указывающий, что тип безопасен для перемещения из одного потока в другой. Так что, еслиFoo
небезопасно отправлять в другой поток, то безопасного способа сделать это нет.
Ответ №1:
Send
и Sync
продолжить Foo
. Но важно знать, можете ли вы это сделать.
Это требование непосредственно вытекает из определения downcast_rs::DowncastSync
, которое требует, чтобы все реализующие структуры (т. е. Foo
) были Send
и Sync
. Реализация Send
и Sync
на любом другом типе, таком как SafeFoo
, вам не поможет.
Отправка и синхронизация
Очень важно заметить, что эти черты unsafe
необходимо реализовать! Это означает, что вы никогда не должны их реализовывать, если вы не уверены на 100%, что это правильно, иначе вы можете вызвать UB.
Arc
и Mutex
специально разработаны для использования в разных потоках, поэтому у них, конечно, уже есть реализации для Send
и Sync
(например, Send
включение Arc
и включение Mutex
). Однако у них есть важные ограничения на их внутренний тип.
Поэтому, если вы окажетесь в ситуации, когда либо a Arc
Mutex
, либо отсутствует Send
Sync
реализация или, это никогда Arc
Mutex
не будет проблемой ни с тем, ни с другим, это проблема с внутренним типом! Никогда не внедряйте Send
или Sync
сами Arc
, или обертку вокруг этого, это определенно UB!
Таким образом, единственное, что действительно имеет значение Foo
, — это то, может ли быть рассмотрено Send
и Sync
, следовательно, является ли реализация Send
или Sync
включение Foo
разумной (т. Е. Правильной). Конечно, важно, чтобы вы понимали, о чем Send
Sync
идет речь, поэтому прочитайте документы: отправить, синхронизировать.
Указатели
Вы также должны знать, что многие типы создаются автоматически Send
и Sync
без вашего участия. Однако есть одно важное исключение из этого правила: указатели!
Указатели на самом деле явно определены как ни Send
, ни Sync
, и это несмотря на то, что указатель как таковой может быть совершенно Send
и Sync
. Поэтому, если у вас есть структура, содержащая указатель, возможно, вы можете реализовать Send
и/или Sync
использовать ее. Но это зависит от того, что вы делаете с этим указателем.
В Rustonomicon есть следующее, что можно сказать об указателях и Send
и Sync
:
Однако необработанные указатели, строго говоря, помечены как небезопасные для потоков, как нечто большее, чем ворс. Выполнение чего-либо полезного с необработанным указателем требует его разыменования, что уже небезопасно. В этом смысле можно утверждать, что было бы «хорошо», если бы они были помечены как потокобезопасные.
Итак, это продолжается с:
Однако важно, чтобы они не были потокобезопасными, чтобы типы, содержащие их, не были автоматически помечены как потокобезопасные. Эти типы имеют нетривиальное неотслеживаемое владение, и маловероятно, что их автор обязательно сильно задумывался о безопасности потоков
Краткие сведения
Сможете ли вы заставить свой код работать правильно, зависит от того, что вы делаете со своим указателем Foo
. Если вы уверены, что сможете реализовать Send
и Sync
на нем, сделайте это, и ваш код будет работать. Если нет, то ваш код просто в корне испорчен, и даже если вы заставите его скомпилироваться с некоторыми unsafe
здесь и там, он будет полностью сломан! Я имею в виду, если вы не можете делиться Foo
между потоками, не делайте этого.
Возьмем, к примеру, ваш код. Вы создаете указатель на стек основных потоков. Если вы когда-нибудь захотите разыменовать x
, Foo
не должно быть ни Send
того , ни Sync
другого, и из этого следует, что вы никогда не должны делиться им или перемещать его между потоками.
Решение
С другой стороны, если мы предположим, что у нас есть право собственности на значение, стоящее за указателем (аналогично a Box
), то x
оно не может быть освобождено или что-либо другое другим потоком, и мы действительно можем перемещать его между потоками и по-прежнему разыменовывать x
.
Теперь, Foo
по крайней Send
мере, наличия a на самом деле достаточно, чтобы разделить его между потоками, если это a Arc<Mutex<Foo>>
, потому Mutex
что есть Send
и Sync
если Foo
есть, по крайней мере Send
. А что касается унылой части, то на самом деле нам это не нужно DowncastSync
, если мы просто хотим передать его и использовать за a Mutex
, мы можем просто использовать Downcast
.
Таким образом, слегка измененное Foo
может заставить это работать:
use downcast_rs::{Downcast, impl_downcast};
use std::sync::{Arc, Mutex};
use std::any::Any;
trait Base: Downcast {}
impl_downcast!(Base);
#[derive(Debug)]
struct Foo {
/// A `u8` that is owned by this Foo, like a `Box<u8>`
x: *mut u8
}
impl Foo {
pub fn new(inner: Box<u8>) -> Self {
Foo {
x: Box::into_raw(inner)
}
}
pub fn access(amp;self) -> u8 {
unsafe {
// This is sound, because we 'own' x so only we have access
*self.x
}
}
}
// `x` is 'owned' (like a Box) so we may move it between threads,
// without fearing that it to gets deallocated
unsafe impl Send for Foo {}
impl Base for Foo {}
fn main() {
let x = Box::new(42);
// Move x into Foo (no references)
let foo = Foo::new(x);
// Create a shareable Foo
// Notice that `base` is Send amp; Sync
let base: Arc<Mutex<dyn Base Send>> = Arc::new(Mutex::new(foo));
// Move it to an other thread
std::thread::spawn(move || {
// Lock mutex
let guard = base.lock().unwrap();
// Coerce to a simple trait object, so we can call trait-methods on it
let my_base: amp;dyn Base = amp;*guard;
if let Some(foo) = my_base.downcast_ref::<Foo>() {
println!("{:?} = {}", foo, foo.access());
} else {
println!("Not a Foo");
}
}).join();
}