Использование HashMap без синхронизации в асинхронном fn

У меня есть трейт репозитория с методами get и set, и я пытаюсь создать реализацию этого трейта, которая охватывает другую реализацию этого трейта. Я использую async, поэтому все должно быть Send, но мне все равно на Sync. Хорошо, если этот код работает только в одном потоке.

Однако в моей оболочке я продолжаю получать ошибки, связанные с тем, что HashMap используется в оболочке get, не являющейся Sync. Я не уверен, почему это проблема. Я думаю, технически get можно было бы вызывать параллельно, но этот доступ также был бы неизменяемым, так что это не имело бы значения? И кроме того, я хочу, чтобы любой параллелизм обрабатывался на более высоком уровне, поэтому ошибка должна происходить не здесь, а в том месте, где эта функция будет вызываться параллельно.

Я смог заставить его работать, вставив HashMap в Mutex, но этого я очень хотел бы избежать.

Ссылка на игровую площадку

use std::{marker::PhantomData, collections::HashMap};
use async_trait::async_trait;

#[derive(PartialEq, Eq, Hash)]
struct IVec3 {
    pub x: i32,
    pub y: i32,
    pub z: i32
}

trait VoxelComponent : Copy + Send {}
enum VoxelRepositoryError{}

#[async_trait]
trait VoxelRepository<T: VoxelComponent> : Send {
    async fn get(&self, position: IVec3) -> Result<Option<T>, VoxelRepositoryError>;
    async fn set(&mut self, position: IVec3, component: Option<T>) -> Result<(), VoxelRepositoryError>;
}

struct DiffVoxelRepository<R: VoxelRepository<C>, C: VoxelComponent> {
    source_repository: R,
    cache: HashMap<IVec3, C>,
    _component: PhantomData<fn() -> C>
}

impl<R: VoxelRepository<C>, C: VoxelComponent> DiffVoxelRepository<R, C> {
    fn new(repository: R) -> DiffVoxelRepository<R, C> {
        DiffVoxelRepository {
            source_repository: repository,
            cache: HashMap::new(),
            _component: PhantomData
        }
    }
}

#[async_trait]
impl<R: VoxelRepository<C>, C: VoxelComponent> VoxelRepository<C> for DiffVoxelRepository<R, C> {
    
    async fn get(&self, position: IVec3) -> Result<Option<C>, VoxelRepositoryError> {
        match self.cache.get(&position) {
            Some(value) =>  Ok(Some(*value)),
            None => self.source_repository.get(position).await
        }
    }

    async fn set(&mut self, position: IVec3, component: Option<C>) -> Result<(), VoxelRepositoryError>{
        match component {
            Some(c) => self.cache.insert(position, c),
            None => self.cache.remove(&position)
        };

        self.source_repository.set(position, component).await
    }
}
error: future cannot be sent between threads safely
  --> src/lib.rs:39:85
   |
39 |       async fn get(&self, position: IVec3) -> Result<Option<C>, VoxelRepositoryError> {
   |  _____________________________________________________________________________________^
40 | |         match self.cache.get(&position) {
41 | |             Some(value) =>  Ok(Some(*value)),
42 | |             None => self.source_repository.get(position).await
43 | |         }
44 | |     }
   | |_____^ future created by async block is not `Send`
   |
note: captured value is not `Send` because `&` references cannot be sent unless their referent is `Sync`
  --> src/lib.rs:39:19
   |
39 |     async fn get(&self, position: IVec3) -> Result<Option<C>, VoxelRepositoryError> {
   |                   ^^^^ has type `&DiffVoxelRepository<R, C>` which is not `Send`, because `DiffVoxelRepository<R, C>` is not `Sync`
   = note: required for the cast from `[async block@src/lib.rs:39:85: 44:6]` to the object type `dyn Future<Output = Result<Option<C>, VoxelRepositoryError>> + Send`
help: consider further restricting this bound
   |
37 | impl<R: VoxelRepository<C> + std::marker::Sync, C: VoxelComponent> VoxelRepository<C> for DiffVoxelRepository<R, C> {
   |                            +++++++++++++++++++

error[E0277]: `C` cannot be shared between threads safely
  --> src/lib.rs:39:85
   |
39 |       async fn get(&self, position: IVec3) -> Result<Option<C>, VoxelRepositoryError> {
   |  _____________________________________________________________________________________^
40 | |         match self.cache.get(&position) {
41 | |             Some(value) =>  Ok(Some(*value)),
42 | |             None => self.source_repository.get(position).await
43 | |         }
44 | |     }
   | |_____^ `C` cannot be shared between threads safely
   |
   = note: required because it appears within the type `(IVec3, C)`
   = note: required for `hashbrown::raw::RawTable<(IVec3, C)>` to implement `Sync`
   = note: required because it appears within the type `HashMap<IVec3, C, RandomState>`
   = note: required because it appears within the type `HashMap<IVec3, C>`
note: required because it appears within the type `DiffVoxelRepository<R, C>`
  --> src/lib.rs:20:8
   |
20 | struct DiffVoxelRepository<R: VoxelRepository<C>, C: VoxelComponent> {
   |        ^^^^^^^^^^^^^^^^^^^
   = note: required for `&DiffVoxelRepository<R, C>` to implement `Send`
note: required because it's used within this `async` block
  --> src/lib.rs:39:85
   |
39 |       async fn get(&self, position: IVec3) -> Result<Option<C>, VoxelRepositoryError> {
   |  _____________________________________________________________________________________^
40 | |         match self.cache.get(&position) {
41 | |             Some(value) =>  Ok(Some(*value)),
42 | |             None => self.source_repository.get(position).await
43 | |         }
44 | |     }
   | |_____^
   = note: required for the cast from `[async block@src/lib.rs:39:85: 44:6]` to the object type `dyn Future<Output = Result<Option<C>, VoxelRepositoryError>> + Send`
help: consider further restricting this bound
   |
37 | impl<R: VoxelRepository<C>, C: VoxelComponent + std::marker::Sync> VoxelRepository<C> for DiffVoxelRepository<R, C> {
   |          

Но возможность поделиться &T между потоками — это именно то, что означает T: Sync. Представьте, что у T есть какая-то внутренняя изменчивость (RefCell или Rc), тогда вы разделяете &T между потоками и... состоянием гонки! Если вы знаете, что этого просто не произойдет, вы должны сообщить об этом компилятору с помощью T: Sync.

rodrigo 15.04.2023 15:27
Асинхронная передача данных с помощью sendBeacon в JavaScript
Асинхронная передача данных с помощью sendBeacon в JavaScript
В современных веб-приложениях отправка данных из JavaScript на стороне клиента на сервер является распространенной задачей. Одним из популярных...
0
1
52
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я использую async, поэтому все должно быть Send, но мне все равно на Sync. Хорошо, если этот код работает только в одном потоке.

Использование async не требует от вас использования Send — этого требуют только многопоточные асинхронные исполнители. Однако библиотека async_trait по умолчанию предполагает, что вы хотите Send для всех фьючерсов. Вы можете попросить его не с #[async_trait(?Send)].

Следующая версия вашей программы компилируется со следующими изменениями:

  • Удалены все границы Send.
  • Заменил #[async_trait] на #[async_trait(?Send)].
  • Добавлено derive(Clone, Copy) в IVec3. (Это несвязанная проблема, обнаруженная после того, как другие были исправлены.)

Если вместо этого вы хотите сохранить Send границы, то вам действительно нужно будет добавить Sync границы к типам, с которыми вы работаете, через & ссылку из Send фьючерсов, потому что, как правило, &T: Send только когда T: Sync, поэтому с помощью &T ссылки из Send будущего требует T реализации Sync. Send и Sync тесно связаны, и обычно вы не можете использовать Send, не используя иногда Sync.

use std::{marker::PhantomData, collections::HashMap};
use async_trait::async_trait;

#[derive(Clone, Copy, PartialEq, Eq, Hash)]
struct IVec3 {
    pub x: i32,
    pub y: i32,
    pub z: i32
}

trait VoxelComponent : Copy {}
enum VoxelRepositoryError{}

#[async_trait(?Send)]
trait VoxelRepository<T: VoxelComponent> {
    async fn get(&self, position: IVec3) -> Result<Option<T>, VoxelRepositoryError>;
    async fn set(&mut self, position: IVec3, component: Option<T>) -> Result<(), VoxelRepositoryError>;
}

struct DiffVoxelRepository<R: VoxelRepository<C>, C: VoxelComponent> {
    source_repository: R,
    cache: HashMap<IVec3, C>,
    _component: PhantomData<fn() -> C>
}

impl<R: VoxelRepository<C>, C: VoxelComponent> DiffVoxelRepository<R, C> {
    fn new(repository: R) -> DiffVoxelRepository<R, C> {
        DiffVoxelRepository {
            source_repository: repository,
            cache: HashMap::new(),
            _component: PhantomData
        }
    }
}

#[async_trait(?Send)]
impl<R: VoxelRepository<C>, C: VoxelComponent> VoxelRepository<C> for DiffVoxelRepository<R, C> {
    
    async fn get(&self, position: IVec3) -> Result<Option<C>, VoxelRepositoryError> {
        match self.cache.get(&position) {
            Some(value) =>  Ok(Some(*value)),
            None => self.source_repository.get(position).await
        }
    }

    async fn set(&mut self, position: IVec3, component: Option<C>) -> Result<(), VoxelRepositoryError>{
        match component {
            Some(c) => self.cache.insert(position, c),
            None => self.cache.remove(&position)
        };
    
       self.source_repository.set(position, component).await
    }
}

Почему ты пишешь #[async_trait(?Send)] ? с дополнительным аргументом? Согласно его источникам, он не использует этот docs.rs/async-trait/latest/src/async_trait/lib.rs.html#341

unegare 15.04.2023 19:30

@unegare Я следил за документацией и подтвердил, что она скомпилирована. Но также в коде вы можете видеть, что args передается в impl Parse for Args, который затем ищет ?Send или ничего.

Kevin Reid 15.04.2023 23:23

Другие вопросы по теме