Какое значение атомная операция чтения с memory_order_seq_cst читает в этой ситуации?

Я прочитал главы об упорядочении памяти в стандарте c ++ 11 и смущен правилом. Согласно стандарту C ++ 11 (ISO / IEC JTC1 SC22 WG21 N3690), 29,3 3, сказано, что:

Должен быть единый общий порядок S для всех операций memory_order_seq_cst, соответствующий порядку «происходит раньше» и порядкам модификации для всех затронутых местоположений, так что каждая операция memory_order_seq_cst B, которая загружает значение из атомарного объекта M, наблюдает одно из следующих значений :
— результат последней модификации A в M, которая предшествует B в S, если она существует, или
— если A существует, результат некоторой модификации M в видимой последовательности побочных эффектов относительно B, который не является memory_order_seq_cst и которого не происходит до A, или
— если A не существует, результатом некоторой модификации M в видимой последовательности побочных эффектов относительно B, которая не является memory_order_seq_cst.

Итак, рассмотрим следующую ситуацию:

Есть 4 атомных операции , В, С, D.

  • Все они являются операциями над одной и той же атомарной переменной.
  • а также В являются операциями записи с любым порядком (могут быть ослаблены)
  • С это операция записи с memory_order_seq_cst
  • D это операция чтения с memory_order_seq_cst
  • последняя операция записи, которая случается, перед тем D
  • , В, С не бывает до того, как отношения взаимно.
  • D, В, С не бывает до того, как отношения взаимно.
  • С появляется раньше D в едином общем порядке для операций memory_order_seq_cst
  • Порядок модификации этой переменной выглядит следующим образом ->В->С

Вот возможный код

using namespace std;

atomic_bool go(false);
atomic_int var(0);

void thread1()
{
while (!go) {}
var.store(1, memory_order_relaxed);              // A
this_thread::yield();
cout << var.load(memory_order_seq_cst) << endl;  // D
}

void thread2()
{
while (!go) {}
var.store(2, memory_order_seq_cst);              // C
}

void thread3()
{
while (!go) {}
var.store(3, memory_order_relaxed);              // B
}

int main() {
thread t1(thread1);
thread t2(thread2);
thread t3(thread3);
go = true;
t1.join();
t2.join();
t3.join();
}

Тогда, мой вопрос, возможно ли, что операция чтения D будет читать значение, записанное операцией В? Если это невозможно, какие правила исключают эту возможность? Если возможно, это означает, что memory_order_seq_cst может прочитать значение, «записанное до» последней записи в memory_order_seq_cst. Является ли это «ошибкой» в стандарте c ++ или разработано намеренно?

1

Решение

В этом случае возможно, что D читает из A, B или C.

Рассмотрим граф с четырьмя узлами: A, B, C и D.
И ребра (sc: последовательное согласованное (общее) упорядочение (C —sc -> D), sb: упорядочено до / произойдет до (A —sb -> D), mo: порядок модификации (A —mo- -> B —mo -> C) и rf: чтение из (? —Rf -> D)).

Есть две причины, по которым рф-грань на графике не согласуется с моделью памяти C ++: причинность и потому, что вы не можете прочитать скрытые визуальные побочные эффекты.

Если вы на мгновение игнорируете ребра sc, то — имея только одну атомарную переменную, единственным причинным ограничением на графе является отсутствие петель, включающих ВЧ ребра и (направленных) ребер sb (это результат мое исследование). В этом случае ни один такой цикл не может даже существовать, поскольку у вас есть только один радиочастотный фронт — так что нет никаких причин, по которым вы не можете прочитать ни одну из трех записей.

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

Обратите внимание, что данный рф-фронт вводит синхронизацию, если его узел записи освобожден, а считывающий узел получен; sc — освобождение / получение, поэтому последнее верно, а первое — только при чтении с узла C. Однако эта синхронизация означает, что никогда больше, чем (в порядке изменения) все до записи должно происходить раньше всего после чтения; и после чтения ничего не происходит, поэтому вся синхронизация не имеет значения.

Более того, продиктованный порядок модификации (A —mo -> B —mo -> C) не является причинно несовместимым с продиктованным общим порядком sc (C —sc -> D), потому что D — это чтение, а не часть порядка изменения подграфа. Единственное, что не допускается (из-за причинности) — это направленные петли, включающие ребра sc и mo.

Теперь, в качестве эксперимента, предположим, что мы делаем узел A также sc. Затем нам нужно поставить A в полном порядке, таким образом, либо A —sc -> C —sc -> D, C —sc -> A —sc -> D, либо C —sc- -> D —sc -> A, но у нас есть A —mo -> C, поэтому последние два не допускаются (вызовут (причинный) цикл), и единственно возможный порядок: A —sc- -> C —sc -> D. Теперь больше невозможно читать из A, потому что это приведет к следующему подграфу:

A --sc--> C
|        /
|       /
|      /
rf    sc
|    /
|   /
|  /
v v
D

и запись в C всегда будет перезаписывать значение, которое было написано A, прежде чем оно будет прочитано D (иначе, A — скрытый визуальный побочный эффект для D).

Если A не sc (как в оригинальной задаче), тогда этот rf запрещен (из-за скрытого все), когда

A --hb--> C
|        /
|       /
|      /
rf    sc
|    /
|   /
|  /
v v
D

где ‘hb’ означает Happens-Before (по той же причине; тогда A — скрытый визуальный побочный эффект для D, так как C всегда перезаписывает значение, записанное A, прежде чем D его прочитает).

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

Наконец, да, это намеренное поведение, а не ошибка в стандарте.

редактировать

Чтобы процитировать стандарт, который вы цитировали:

— the result of the last modification A of M that precedes B in S, if it exists, or

A здесь — это ваш C, а B — это ваш D. A, упомянутый здесь, существует, а именно узел C (C —sc -> D). Таким образом, эта строка говорит о том, что можно прочитать значение, записанное узлом C.

— if A exists, the result of some modification of M in the visible sequence of side effects with respect to B that is not memory_order_seq_cst and that does not happen before A, or

Опять же, А вот ваш С, и он существует. Тогда «результат некоторой модификации M (var) в видимой последовательности побочных эффектов относительно B (вашего D), который является не memory_order_seq_cst «- это ваш А. И, как мы уже выяснили, ваш A не встречается до вашего C (их A). Таким образом, это говорит о том, что можно прочитать значение, записанное из вашего A.

— if A does not exist, the result of some modification of M in the visible sequence of side effects with respect to B that is not memory_order_seq_cst.

Это не имеет значения здесь и будет применяться только тогда, когда не было записи в общем порядке S в M (var), предшествовавшем B (вашему D).

0

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]