Понижение MyStruct: !Синхронизация !Отправить, но Arc<Мьютекс> — это Отправить Синхронизация

#rust

Вопрос:

Я использую ящик downcast_rs для передачи вниз с Arc<Mutex<dyn MyTrait>> на Arc<Mutex<MyConcreteObject>>

Обычно , когда у нас есть не Sync Send тип , например MyStruct , давайте предположим, что это потому, что он содержит указатель *mut u8 , мы на самом деле можем Arc<Mutex<MyStruct>> считать его Sync Send и делать что-то вроде этого:

 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 for Foo приведет к неопределенному поведению, то включение этого Arc<Mutex<Foo>> не решит проблему. Send это просто маркер, указывающий, что тип безопасен для перемещения из одного потока в другой. Так что, если Foo небезопасно отправлять в другой поток, то безопасного способа сделать это нет.

Ответ №1:

TL;DR В принципе, то, что вы можете сделать, чтобы решить свою проблему, — это реализовать 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();
}