Я спрашиваю конкретно в смысле модели памяти. http://en.cppreference.com/w/cpp/atomic/memory_order
Я спрашиваю, потому что я хочу знать, могу ли я использовать std::memory_order_consume
ниже:
mLocalMemPtr1 и 2 и mAtomicMemPtr являются указателями на общий буфер.
В теме продюсера я делаю:
for (int x = 0; x < 10; ++x)
{
++mLocalMemPtr1
*mLocalMemPtr1 = x; // <========= A
mAtomicMemPtr.store(mLocalMemPtr1, std::memory_order_release);
}
А у потребителя:
tempMemPtr = mAtomicMemPtr.load(std::memory_order_consume);
while (tempMemPtr != mLocalMemPtr2)
{
++mLocalMemPtr2;
int test = *mLocalMemPtr2; // <======== B
doSomeLongRunningThing(test);
tempMemPtr = mAtomicMemPtr.load(std::memory_order_consume);
}
Так идет ли цепочка зависимостей tempMemPtr -> mLocalMemPtr2 -> test -> doSomeLongRunningThing?
Я особенно обеспокоен тем, что B
может быть выполнен раньше A
, Я знаю, что могу использовать std::memory_order_acquire
, но я могу использовать потребление (что является более легким), если условный оператор вызывает зависимость порядка памяти.
Релиз-Потребление заказа
Если элементарное хранилище в потоке A помечено как std :: memory_order_release, а атомарная загрузка в потоке B из той же переменной помечена как std :: memory_order_consume, все записи в память (неатомарные и расслабленные атомарные), которые Зависимость упорядоченная-перед тем атомарные хранилища с точки зрения потока A становятся видимыми побочными эффектами в потоке B, то есть после завершения атомарной загрузки поток B гарантированно увидит все, что поток A записал в память, если он несет в себе зависимость данных в атомную нагрузку.
1.10.10:
Оценка A упорядочена по зависимости перед оценкой B, если
— A выполняет операцию освобождения атомарного объекта M, а в другом потоке B выполняет операцию потребления для M и читает значение, записанное любым побочным эффектом в последовательности выпуска, возглавляемой A (…)
1.10.9:
Оценка A несет зависимость от оценки B, если — значение A используется в качестве операнда B, если:
— B является вызовом любой специализации std :: kill_dependency (29.3), или
— A — левый операнд встроенного логического AND (&&, см. 5.14) или логический оператор ИЛИ (||, см. 5.15), или
— A — левый операнд условного оператора (?:, См. 5.16), или
— A — левый операнд встроенного оператора запятой (,) (5.18); (…)
Основываясь на этих фактах, я говорю, что mLocalMemPtr2
должны быть синхронизированы. Однако все еще остается вопрос о порядке оценки.
if (atomic.load(std::consume) < x)
Какой из них оценивается первым, не уточняется. Есть без гарантии (как я не нашел в стандарте), что компилятор сначала выполнит операцию потребления, обновит общий буфер и затем загрузит atomic
а потом x
,
Не найдя доказательства того, что операнды вычисляются «желаемым» способом, я говорю, что без явного разложения атомной нагрузки mLocalMemPtr2
это не будет работать, и процессор может прочитать устаревшее значение памяти, на которое указывает mLocalMemPtr2
, memory_order_acquire
не сильно изменится здесь, так как mLocalMemPtr2
несет в себе зависимость от данных.
Я считаю, что с consume
при заказе компилятор может сделать копию всего mSharedBuffer
заблаговременно. Тебе нужно acquire
семантика для аннулирования ранее кэшированных копий переменных, кроме mAtomicMemLocPtr
,
Если рассматривать вещи буквально так, как указано в несет зависимость . Тогда я бы сказал, что
tempMemPtr = mAtomicMemPtr.load(std::memory_order_consume);
переносит зависимость в условие (думайте об этом как флаг логической переменной) цикла. Но это условие не читается как операнд ни в одной из операций тела цикла (и при этом оно не записывается в объект, который читается другой операцией в теле цикла). Таким образом, необходима операция получения, чтобы операции, которые упорядочены до выпуска, также происходили перед операциями после получения (которые читают этот выпуск записи), не полагаясь на зависимость данных между операцией потребления и операциями в теле петля
Может быть, решение, которое включает семантику потребления релиза:
tempMemPtr = mAtomicMemPtr.load(std::memory_order_consume);
while (tempMemPtr != mLocalMemPtr2)
{
mLocalMemPtr2 = tempMemPtr; // that line adds the dependency needed
++mLocalMemPtr2;
int test = *mLocalMemPtr2;
doSomeLongRunningThing(test);
tempMemPtr = mAtomicMemPtr.load(std::memory_order_consume);
}