Задать вопрос
Eugene-Usachev
@Eugene-Usachev

Можно ли использовать не парные Acquire/Release порядки памяти?

Я потратил несколько часов на изучения темы порядков памяти, и у меня в голове остались некоторые противоречия. Одно из них о Acquire/Release порядках памяти. Сейчас у меня в голове есть такие два утверждения о них:

1 — Никакие операции после Acquire не могут быть переставлены выше это операции, когда как никакие операции до Release не могут быть переставлены после неё;
2 — Использованные на одной области памяти порядки Acquire/Release позволяют операциям после Acquire видеть все побочные операции (side affects) до Release.

А также есть наблюдение и слова других людей, что Acquire/Release работают только в паре. И тут у меня немного ломается представление модели в голове. Пускай я реализую SPSC кольцевую очередь. И у меня есть два метода: maybe_push и consumer_pop_many.

Код на Rust

pub unsafe fn consumer_pop_many(&self, dst: &mut [MaybeUninit<T>]) -> usize {
        let head = unsafe { self.head.unsync_load() }; // only consumer can change head
        let tail = self.tail.load(Acquire); // (1)
        let available = Self::len(head, tail);
        let n = dst.len().min(available);

        if n == 0 {
            return 0;
        }

        let dst_ptr = dst.as_mut_ptr();
        let head_idx = head as usize % CAPACITY;
        let right = CAPACITY - head_idx;

       // copy data

        self.head.store(head.wrapping_add(n as LongNumber), Release); // (2)

        n
    }

    pub unsafe fn producer_maybe_push(&self, value: T) -> Result<(), T> {
        let tail = unsafe { self.tail.unsync_load() }; // only the producer can change tail
        let head = self.head.load(Relaxed); // (3)

        if unlikely(Self::len(head, tail) == CAPACITY) {
            return Err(value);
        }

        debug_assert!(Self::len(head, tail) < CAPACITY);

        unsafe { 
            self.buffer_mut_thin_ptr()
                .add(tail as usize % CAPACITY)
                .write(MaybeUninit::new(value));

            self.tail.store(tail.wrapping_add(1), Release); // (4)
       };

        Ok(())
    }



Код на C++

bool consumer_pop_many(T* dst, size_t& count) {
        Index head_val = head.unsync_load();   // only consumer can change head
        Index tail_val = tail.load(std::memory_order_acquire);   // (1)

        size_t available = len(head_val, tail_val);
        size_t n = std::min(count, available);

        if (n == 0) {
            count = 0;
            return false;
        }

        size_t head_idx = static_cast<size_t>(head_val % CAPACITY);
        size_t right = CAPACITY - head_idx;

        // copy data

        head.store(head_val + n, std::memory_order_release);     // (2)

        count = n;
        return true;
    }

    bool producer_maybe_push(T&& value) {
        Index tail_val = tail.unsync_load();   // only the producer can change tail
        Index head_val = head.load(std::memory_order_relaxed);   // (3)

        if (len(head_val, tail_val) == CAPACITY) {
            return false;
        }

        size_t idx = static_cast<size_t>(tail_val % CAPACITY);
        new (&buffer[idx]) T(std::move(value));

        tail.store(tail_val + 1, std::memory_order_release);     // (4)
        return true;
    }



В 1 я использую Acquire, чтобы увидеть запись от 4 (побочный эффект). Все остальные операции зависят от этой загрузки и не будут переупорядочены до неё в любом случае, но Acquire даёт и такую гарантию тоже.

В 2 я использую Release, чтобы операция перекопирования произошла точно раньше передвижения головы.

В 4 я использую Release, чтобы запись произошла точно раньше и чтобы 1 видел эту запись.

Но вот могу ли я использовать Relaxed в 3? Мне всё равно на перемещение инструкций в этом методе, так как tail я могу загрузить и позднее, а все остальные операции зависят от результата этой. Но что насчёт побочных эффектов? Чтение может быть побочным эффектом? Оно упорядочено Release в consumer_pop_many, но может ли возникнуть ситуация, когда первый поток прочитал и сдвинул голову, а второй поток прочитал голову, но сделал это до чтения первым потоком? Выглядит, будто это невозможно, но примеры с SeqCst показывают интересные ситуации, так что этот вопрос я не могу выбросить у себя из головы.
  • Вопрос задан
  • 49 просмотров
Подписаться 1 Простой Комментировать
Пригласить эксперта
Ответы на вопрос 2
Daemon23RUS
@Daemon23RUS
Я потратил несколько часов на изучения темы порядков памяти, и у меня в голове остались некоторые противоречия.
Тратьте еще некоторое время, документ по ссылке поможет https://www.kernel.org/doc/Documentation/memory-ba...
P.S. По диагонали глянул код, вспомнил информацию, которая мне помогла в свое время, чем с Вами и делюсь.
Ответ написан
Комментировать
jcmvbkbc
@jcmvbkbc
"I'm here to consult you" © Dogbert
могу ли я использовать Relaxed в 3?

Если тебе не важно то, что ты при этом можешь пропустить изменение head, то можешь. Но тебе должно быть важно, потому что это же единственная загрузка head в producer_maybe_push().
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы