Приобретение/выпуск VS Sequential Consistency в 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, но код этого не делает.
  • Acquire может предотвратить изменение порядка LoadLoad.
  • Выпуск может предотвратить повторный заказ StoreStore.

person Pengcheng    schedule 22.05.2018    source источник
comment
связанные, возможно дублирующиеся: Будут ли две атомарные записи в разные места в разных потоках всегда отображаться в одном и том же порядке другими потоками? - yes acq/ rel позволяет переупорядочивать IRIW, в отличие от seq_cst. Настоящая HW, которая может сделать это на практике, встречается редко, может быть, только POWER.   -  person Peter Cordes    schedule 09.01.2020


Ответы (2)


Да, утверждение может сработать.

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

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

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

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

person Caleth    schedule 22.05.2018
comment
Это не то, как работают настоящие многопроцессорные системы. В реальной жизни они всегда согласуются с кешем (иначе мы не запускаем для них std::threads); новое значение вообще не может стать видимым для других физических ядер (в одном пакете или на другом сокете), пока записывающее ядро ​​не получит монопольное право владения строкой кэша (состояние MESI Modified), что делает недействительными все устаревшие копии before< /i> фиксация хранилища. - person Peter Cordes; 09.01.2020
comment
Фактический механизм переупорядочения IRIW в POWER аналогичен тому, что вы описали, но механизм «некоторые потоки видят его на раннем этапе» (до того, как он станет глобально видимым путем фиксации в кэше L1d) — это переадресация хранилища. между логическими ядрами физического ядра SMT (например, гиперпоточность). Будут ли две атомарные записи в разные места в разных потоках всегда видны другим потокам в одном и том же порядке?. seq_cst предотвращает это с помощью комбинации барьеров, которые, как я полагаю, гарантируют, что загрузки вообще не будут пересылаться из буфера хранилища; дополнительный барьер перед нагрузками SC - person Peter Cordes; 09.01.2020
comment
Я думаю, что этот ответ может выиграть от добавления простого Да, утверждение может срабатывать. - person Ari; 29.07.2020
comment
@PeterCordes seq_cst предотвращает это с помощью комбинации барьеров, которые, как я полагаю, гарантируют, что загрузки вообще не перенаправляются из буфера хранилища; дополнительный барьер перед загрузкой SC; Я считаю, что барьер (полный/синхронный/hwsync) между загрузками SC также работает, см. здесь (лакмусовая бумажка IRIW+dmbs/syncs). Компилятор C++11 может вставлять конечные границы, если граница размещается после первой загрузки. Первая загрузка все еще может быть перенаправлена ​​из буфера хранилища, но вторая загрузка выполняется только после того, как первая загрузка станет видна глобально. - person Daniel Nitzan; 29.01.2021

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

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

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

person BeeOnRope    schedule 28.05.2018
comment
Это, вероятно, замалчивается, потому что самые сильные барьеры, предотвращающие любое изменение порядка (включая #SL), обычно применяют seq/cst. - person LWimsey; 28.05.2018
comment
@LWimsey Да, но, например, в Intel даже без ограждений вы получаете постоянный порядок IRIW, но им потребовалось много времени, чтобы задокументировать и гарантировать это. Вместо этого они начали с этого заявления о причинно-следственной согласованности, но оно было довольно бесполезным, и, в конце концов, после указания на то, что существующие фреймворки основаны на отсутствии IRIW, и довольно сложно программировать для арок с переупорядочением IRIW, они в конечном итоге гарантировали это (предоставив нам так- называется моделью x86-TSO). - person BeeOnRope; 28.05.2018
comment
Это правда, будучи TSO. Но насколько я понимаю, Intel (до сих пор?) официально не заявляет, что она является настоящим TSO. Просто кажется, что он так себя ведет. - person LWimsey; 28.05.2018
comment
Они документируют это как TSO (хотя и не используют этот точный термин) в течение последних нескольких лет в своих руководствах (поскольку фурор по поводу существующей модели плохо определен): чем те, которые выполняют операции по хранению (раздел 8.2.2). Затем в 8.2.3.7 они также приводят точный канонический пример IRIW с гарантией того, что IR видят IW в согласованном порядке. Пример более или менее является кодом OP. @LWimsey - person BeeOnRope; 28.05.2018
comment
Интересно, они и сейчас... спасибо за референсы, есть что почитать. - person LWimsey; 28.05.2018
comment
Что касается последней части вашего ответа, PowerPC использует только lwsync и isync для приобретения/выпуска. Разве для предотвращения переупорядочения IRIW не требуется полный sync ? (У меня нет доступа к PowerPC, все, что я знаю, это от Godbolt) - person LWimsey; 28.05.2018
comment
@LWimsey - я признаю, что не в курсе операций синхронизации PowerPC, поскольку я не смотрел на них целую вечность. Тем не менее то, что вы говорите, имеет смысл: IRIW разрешается с помощью acq/rel, поэтому, возможно, PowerPC является примером реального случая, когда acq/rel приведет к нарушению порядка IRIW. - person BeeOnRope; 28.05.2018
comment
Я думал о сценарии, в котором атомарный RMW использует порядок memory_order_acq_rel. Можно было бы ожидать, что никакие операции с памятью не должны переупорядочиваться в обоих направлениях (т. е. быть как получением, так и освобождением), и поэтому вы можете утверждать, что это действует как полный барьер, предотвращающий все типы переупорядочения (включая #SL). ). - person LWimsey; 28.05.2018
comment
... Но использование RMW в контексте IRIW может показать, что это не seq/cst, поскольку PowerPC использует lwsync/isync для acq_rel и sync/isync для seq_cst. Это может быть примером того, что полный барьер не обязательно является seq_cst. Но я не уверен, так как документация PowerPC, которую я нашел, утверждает, что lwsync не может предотвратить #SL. Я задал этот вопрос Джеффу Прешингу, и все, что он сказал, было: это сложный вопрос. - person LWimsey; 28.05.2018
comment
@LWimsey - да, я думаю, что это пример полного барьера, не обязательно являющегося seq cst, и это именно то, о чем я говорил выше: просто обсуждение возможных переупорядочений, например. LoadLoad, LoadStore и fiends недостаточно для определения аппаратной модели памяти, потому что все это локальные эффекты: в нем не говорится о том, как должны быть упорядочены записи из разных процессоров. . Очевидно, что два независимых хранилища от разных процессоров не имеют особой связи, но как только они были наблюдаемы в некотором порядке, возникает вопрос, подразумевает ли это... - person BeeOnRope; 28.05.2018
comment
... что-то о порядке, наблюдаемом другими процессорами. То, как это работает, не определяется простым описанием того, какие из 4 классических комбинаций переупорядочения разрешены: у вас может быть даже ЦП с нулевым разрешенным переупорядочением, но где разные ЦП не видят глобальный поток записи. в том же порядке, без необходимости вообще вызывать какое-либо локальное переупорядочение. Таким образом, SPARC (который, насколько мне известно) ввел нотацию типа #LoadStore для барьеров) и x86 имеют общий порядок хранения, чтобы ответить на этот вопрос, но оба рассматривали другие модели в какой-то момент и потребовалось некоторое время, чтобы добраться туда. - person BeeOnRope; 28.05.2018
comment
Другие поставщики, возможно, включая PPC на основе вашего примера, могут не иметь модели TSO, поэтому тогда вопрос усложняется и, вероятно, существуют барьеры, которые имеют нелокальные эффекты, чтобы заставить глобальный порядок в случаях, когда это необходимо. - person BeeOnRope; 28.05.2018
comment
Модели программной памяти, с другой стороны, в основном обходят весь этот вопрос, не основывая свой формальный язык на самом деле на возможных переупорядочениях или барьерах, а используя более абстрактную модель с событиями до и общим порядком записи, последовательностью cst и т. д. Разница между seq_cst и почти seq_cst вещи, такие как ваш mo_acq_rel пример, вероятно, отражают различия в реальном оборудовании: мы знаем, что все поставщики позаботились о том, чтобы это было в основном быстро на их оборудовании (например, беспорядочный порядок consume). - person BeeOnRope; 28.05.2018