2016-02-26 2 views
1

Cppreference дает following example о memory_order_relaxed:и изменение порядка memory_order_relaxed

атомарные операции с тегами memory_order_relaxed не Синхронизация операции, они не приказывают памяти. Они гарантируют только атомарность и согласованность порядка модификации.

Затем объясняет, что, с x и y первоначально нулевым, этот пример кода

// Thread 1: 
r1 = y.load(memory_order_relaxed); // A 
x.store(r1, memory_order_relaxed); // B 

// Thread 2: 
r2 = x.load(memory_order_relaxed); // C 
y.store(42, memory_order_relaxed); // D 

разрешено производить r1 == r2 == 42, потому что:

  1. Хотя А секвенировали-перед тем В пределах нить 1 и C - с последовательностью до D в резьбе 2,
  2. Ничто не мешает D появляться до А в порядке модификации y, а B - до C в порядке модификации x.

Теперь мой вопрос: если А и В не могут быть перераспределены в пределах потока 1 и, аналогично, С и D в пределах резьбы 2 (так как каждый из них является секвенировали-перед тем в пределах своей нити), не являются точками 1 и 2 в противоречии? Другими словами, без переупорядочения (как кажется, как указано в пункте 1), как возможен сценарий в пункте 2, представленный ниже?

Т1 Т2 ...........

.............. Д (у)

А (у)

в (х)

.............. с (х)

Поскольку в данном случае C бы не быть секвенировали-D, прежде чем нити в пределах 2 , как точка 1.

+0

Я бы порекомендовал посмотреть здесь: http://stackoverflow.com/questions/6319146/c11-introduced-a-standardized-memory-model-what-does-it-mean-and-how-is-it- g, особенно ответ об аналогии со специальной теорией относительности. Мое понимание этого заключается в том, что с расслабленным атомом, нет ничего как глобального времени, поэтому вы не можете просто визуализировать действия в некоторой глобальной диаграмме времени. В частности, каждый поток может иметь в определенный момент другой вид памяти. –

ответ

4

, без изменения порядка (как точки 1, кажется, требует)

пункт 1 не значит "не переназначения". Это означает последовательность событий в потоке выполнения.Компилятор выдаст инструкцию CPU для A до B и инструкцию CPU для C до D (хотя даже это может быть отклонено по правилу as-if), но CPU не обязан выполнять их в этом порядке, кэшировать/писать очереди буферов/недействительности не обязаны распространять их в этом порядке, и память не обязана быть единообразной.

(индивидуальные архитектуры могут предложить эти гарантии, хотя)

+0

Итак, _sequenced-before_ применяется только к порядку соответствующих инструкций, выпущенных _ компилятором_, который CPU _can_ переупорядочивает в модели с ослабленной памятью? –

+0

ЦП может переупорядочить все, но инструкции по заграждению (и отношения данных как между A и B) ограничивают то, что он может сделать. – Cubbi

+0

CPU не может переупорядочить * все *, есть правила для каждой архитектуры (а иногда и разные типы mem/operation в одной и той же архитектуре), говорящие о том, что можно переупорядочить. – Leeor

0

Ваша интерпретация текста неверна. Давайте разберем это вниз:

атомарные операции с тегами memory_order_relaxed не являются операции синхронизации, они не приказывают модуля памяти

Это означает, что эти операции не делают никаких гарантий относительно порядка событий. Как объяснялось перед этим утверждением в исходном тексте, многопоточным процессорам разрешено изменять порядок операций в рамках одного потока. Это может повлиять на запись, чтение или и то, и другое. Кроме того, компилятору разрешено делать то же самое во время компиляции (в основном для целей оптимизации). Чтобы увидеть, как это относится к этому примеру, предположим, что мы вообще не используем типы atomic, но мы используем примитивные типы, которые являются атомарными (8-битное значение ...).Давайте перепишем пример:

// Somewhere... 
uint8_t y, x; 

// Thread 1: 
uint8_t r1 = y; // A 
x = r1; // B 

// Thread 2: 
uint8_t r2 = x; // C 
y = 42; // D 

Учитывая как компилятор, и процессор разрешается изменять порядок операций в каждом потоке, это легко увидеть, как x == y == 42 возможно.


Следующая часть утверждения:

Они гарантируют только атомарность и порядок изменения консистенции.

Это означает, что только гарантирует, что каждая операция является атомарной, то есть, невозможно операция, которая будет наблюдаться «середина пути, хотя». Это означает, что если x является atomic<someComplexType>, невозможно, чтобы один поток наблюдал x как имеющее значение между состояниями.


Это уже должно быть понятно, где это может быть полезно, но давайте рассмотрим конкретный пример (для демонстрации предлагает только, это не так, как вы хотите код):

class SomeComplexType { 
    public: 
    int size; 
    int *values; 
} 

// Thread 1: 
SomeComplexType r = x.load(memory_order_relaxed); 
if(r.size > 3) 
    r.values[2] = 123; 

// Thread 2: 
SomeComplexType a, b; 
a.size = 10; a.values = new int[10]; 
b.size = 0; b.values = NULL; 
x.store(a, memory_order_relaxed); 
x.store(b, memory_order_relaxed); 

Что тип atomic для нас является гарантией того, что r в потоке 1 не является объектом между штатами, в частности, что это size & values Свойства находятся в синхронизации.

+0

Не разделяется ли предложение «А» - до того, как B в потоке 1 и C секвенирован - до D в потоке 2 »означает, что ** операции внутри потоков не переупорядочиваются **? –

+0

@ DanielLangr с потоками - да, но CPU и кэширует и последующие исправления памяти. – Cubbi

+0

@Cubbi _ Если A секвенирован до B, тогда оценка A будет завершена до начала оценки B._ Итак, что это правило из cppreference. com точно означает? Предоставляет ли он возможность переупорядочивания ЦП, т. Е. Во время выполнения, может ли B быть оценено до A? –

1

Согласно STR аналогии с этого поста: C++11 introduced a standardized memory model. What does it mean? And how is it going to affect C++ programming?, я создал визуализацию того, что здесь может произойти (как я понимаю) следующим образом:

enter image description here

Thread 1 первый видит y=42, затем он выполняет r1=y и после его x=r1. Нить 2 сначала видит x=r1 уже 42, затем выполняет r2=x, а после - y=42.

Линии представляют собой «виды» памяти по отдельным потокам. Эти строки/представления не могут пересекаться для определенного потока. Но при расслабленной атомизации линии/виды одного потока могут пересекать их из других потоков.

EDIT:

Я предполагаю, что это так же, как со следующей программой:

atomic<int> x{0}, y{0}; 

// thread 1: 
x.store(1, memory_order_relaxed); 
cout << x.load(memory_order_relaxed) << y.load(memory_order_relaxed); 

// thread 2: 
y.store(1, memory_order_relaxed); 
cout << x.load(memory_order_relaxed) << y.load(memory_order_relaxed); 

, который может производить 01 и 10 на выходе (такой выход не может произойти с SC атомной операции).

0

прогностических исключительно на C++ модели памяти (не говоря о компиляторе или аппаратном переназначении), единственном исполнении, что приводит к r1 = r2 = 42:

enter image description here

Здесь я заменил r1 на a и r2 на b. Как обычно, sb обозначает секвенсор-раньше, и это просто порядок между потоками (порядок, в котором инструкции отображаются в исходном коде). Rf являются полями Read-From и означают, что Read/load на одном конце считывает значение, записанное/сохраненное на другом конце.

Для достижения результата необходим цикл, включающий как sb, так и rf-ребра, выделенные зеленым цветом: y записывается в одном потоке, который считывается в другом потоке в a и оттуда, написанном на x, что снова читайте в прежнем потоке в b (который секвенирован - до записи в y).

Существует две причины, по которым построенный граф, подобный этому, был бы невозможным: причинность и потому, что rf считывает скрытый побочный эффект. В этом случае последнее невозможно, потому что мы пишем только один раз для каждой переменной, поэтому ясно, что одна запись не может быть скрыта (перезаписана) другой записью.

Чтобы ответить на вопрос о причинности, мы следуем следующему правилу: цикл запрещен (невозможно), когда он включает в себя одно место памяти, а направление ребер sb находится в одном и том же направлении везде в цикле (направление rf-ребер в этом случае не имеет значения); или, цикл включает в себя более одной переменной, все ребра (sb И rf) находятся в одном и том же направлении, и AT MOST одна из переменных имеет один или несколько rf-ребер между разными потоками, которые не освобождаются/не приобретаются.

В этом случае существует цикл, в котором задействованы две переменные (один край Rf для x и один rf-край для y), все ребра находятся в одном и том же направлении, но TWO-переменные имеют расслабленный/релаксированный rf-край (а именно x и y). Поэтому нет нарушения причинности, и это выполнение, совместимое с моделью памяти C++.

Смежные вопросы