Приобретение / выпуск VS последовательной согласованности в C++11?

#include <thread>
#include <atomic>
#include <cassert>

std::atomic<bool> x = {false};
std::atomic<bool> y = {false};
std::atomic<int> z = {0};

void write_x()
{
    x.store(true, std::memory_order_release);
}

void write_y()
{
    y.store(true, std::memory_order_release);
}

void read_x_then_y()
{
    while (!x.load(std::memory_order_acquire))
        ;
    if (y.load(std::memory_order_acquire)) {
        ++z;
    }
}

void read_y_then_x()
{
    while (!y.load(std::memory_order_acquire))
        ;
    if (x.load(std::memory_order_acquire)) {
        ++z;
    }
}

int main()
{
    std::thread a(write_x);
    std::thread b(write_y);
    std::thread c(read_x_then_y);
    std::thread d(read_y_then_x);
    a.join(); b.join(); c.join(); d.join();
    assert(z.load() != 0);
}

Если я переназначу seq_cst для получения / выпуска в последнем примере cppreference, могу assert(z.load() != 0) потерпеть неудачу?

  • Seq_CST может предотвратить переупорядочение StoreLoad, но код этого не сделал.
  • Приобретение может предотвратить переупорядочение LoadLoad.
  • Выпуск может помешать повторному заказу StoreStore.

2 ответа

Решение

Основное свойство, которое не гарантируется приобретением / выпуском, представляет собой единый общий порядок изменений. Это только гарантирует, что (несуществующие) предыдущие действия a а также b наблюдаются c а также d если они видят true от нагрузок.

Очевидным примером этого является система с несколькими процессорами (физическими сокетами). Die 1 имеет ядро ​​A бегущей нити a и ядро ​​С бегущей нитью c, Умирает 2 имеет ядро ​​B бегущей нити b и ядро ​​D работает поток d, Соединение между двумя сокетами имеет большую задержку по сравнению с операцией памяти, которая попадает в кэш-память.

a а также b бежать в то же время настенные часы. C находится в состоянии покоя с A, поэтому можно увидеть магазин, чтобы x немедленно, но соединение задерживает наблюдение за магазином yТаким образом, он видит старое значение. Точно так же D находится в живых с B, поэтому он видит магазин y, но скучает по магазину x,

Принимая во внимание, что если у вас есть последовательная согласованность, требуется некоторая координация для обеспечения полного порядка, например, "C и D блокируются, пока межсоединение синхронизирует кэши".

Да, возможно, что z.load() == 0 в вашем коде, если вы используете acquire/release заказать, как вы сделали. Между независимыми записями не происходит никаких отношений x а также y, Это не случайное совпадение, использованное в этом примере специально для иллюстрации случая, когда приобретение / выпуск не достаточно.

Это иногда называют IRIW (независимые чтения независимых записей), и в некоторых моделях упорядочения аппаратных средств, как правило, их скрывают. В частности, модель памяти, определяемая только с точки зрения возможной загрузки-загрузки, загрузки-хранения, хранения-хранения и т. Д., Переупорядочений, в действительности ничего не говорит об IRIW. В модели памяти x86 переупорядочение IRIW запрещено из-за предложения, объясняющего, что хранилища имеют общий порядок, и все процессоры просматривают хранилища в этом же порядке.

Я не знаю, допускают ли какие-либо процессоры общего пользования повторный порядок IRIW при использовании барьеров и / или инструкций, необходимых для получения и выпуска, но я не удивлюсь, если некоторые из них это сделают.

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