Ответ 1
Нет, стандартный С++ 11 не гарантирует, что memory_order_seq_cst
предотвращает переупорядочение хранилища non-atomic
вокруг atomic(seq_cst)
.
Даже стандартный С++ 11 не гарантирует, что memory_order_seq_cst
предотвращает переупорядочивание atomic(non-seq_cst)
вокруг atomic(seq_cst)
.
Рабочий проект, стандарт для языка программирования С++ 2016-07-12: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/n4606.pdf
- На всех операциях
memory_order_seq_cst
должен быть один полный порядок S - С++ 11 Стандарт:
§ 29.3
3
Должен быть единый общий заказ S на все memory_order_seq_cst в соответствии с порядком "происходит до" и порядок модификации для всех затронутых местоположений, так что каждый memory_order_seq_cst, которая загружает значение из атомарного объект M наблюдает одно из следующих значений:...
- Но любые атомные операции с упорядочением слабее, чем
memory_order_seq_cst
, не имеют последовательной согласованности и не имеют единого общего порядка, то есть операции безmemory_order_seq_cst
могут быть переупорядочены с помощью операцийmemory_order_seq_cst
в разрешенных направлениях - С++ 11 Стандарт:
§ 29.3
8 [Примечание: memory_order_seq_cst обеспечивает последовательную согласованность только для программы, в которой нет данных, а используется исключительно Операции memory_order_seq_cst. Любое использование более слабых заказов будет аннулировать эту гарантию, если не использовать чрезмерную осторожность. В частности, memory_order_seq_cst заборы обеспечивают общий порядок только для заборов самих себя. Заборы не могут, как правило, использоваться для восстановления последовательных согласованность для атомных операций с более слабыми параметрами упорядочения. - конечная нота]
Также С++ - компиляторы допускают такие переупорядочивания:
- В x86_64
Обычно - если в компиляторах seq_cst реализован как барьер после хранения, то:
STORE-C(relaxed);
LOAD-B(seq_cst);
можно изменить на LOAD-B(seq_cst);
STORE-C(relaxed);
Снимок экрана с Asm, созданный GCC 7.0 x86_64: https://godbolt.org/g/4yyeby
Кроме того, теоретически возможно - если в компиляторах seq_cst реализованы как барьер перед загрузкой, то:
STORE-A(seq_cst);
LOAD-C(acq_rel);
можно переупорядочить до LOAD-C(acq_rel);
STORE-A(seq_cst);
- На PowerPC
STORE-A(seq_cst);
LOAD-C(relaxed);
можно переупорядочить до LOAD-C(relaxed);
STORE-A(seq_cst);
Также на PowerPC может быть такое переупорядочение:
STORE-A(seq_cst);
STORE-C(relaxed);
может быть изменен на STORE-C(relaxed);
STORE-A(seq_cst);
Если даже атомные переменные разрешены для упорядочения по атомам (seq_cst), то неатомные переменные также могут быть переупорядочены по атомам (seq_cst).
Снимок экрана с Asm, созданный GCC 4.8 PowerPC: https://godbolt.org/g/BTQBr8
Подробнее:
- В x86_64
STORE-C(release);
LOAD-B(seq_cst);
можно переупорядочить до LOAD-B(seq_cst);
STORE-C(release);
8.2.3.4 Нагрузки могут быть переупорядочены с более ранними магазинами в разных местах
т.е. Код x86_64:
STORE-A(seq_cst);
STORE-C(release);
LOAD-B(seq_cst);
Можно изменить порядок:
STORE-A(seq_cst);
LOAD-B(seq_cst);
STORE-C(release);
Это может произойти, потому что между c.store
и b.load
не mfence
:
x86_64 - GCC 7.0: https://godbolt.org/g/dRGTaO
С++ и asm-code:
#include <atomic>
// Atomic load-store
void test() {
std::atomic<int> a, b, c;
a.store(2, std::memory_order_seq_cst); // movl 2,[a]; mfence;
c.store(4, std::memory_order_release); // movl 4,[c];
int tmp = b.load(std::memory_order_seq_cst); // movl [b],[tmp];
}
Его можно изменить следующим образом:
#include <atomic>
// Atomic load-store
void test() {
std::atomic<int> a, b, c;
a.store(2, std::memory_order_seq_cst); // movl 2,[a]; mfence;
int tmp = b.load(std::memory_order_seq_cst); // movl [b],[tmp];
c.store(4, std::memory_order_release); // movl 4,[c];
}
Кроме того, последовательная согласованность в x86/x86_64 может быть реализована четырьмя способами: http://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html
LOAD
(без забора) иSTORE
+mfence
LOAD
(без забора) иLOCK XCHG
mfence
+LOAD
иSTORE
(без забора)LOCK XADD
(0) иSTORE
(без забора)
- 1 и 2 пути:
LOAD
и (STORE
+mfence
)/(LOCK XCHG
) - мы рассмотрели выше - 3 и 4 пути: (
mfence
+LOAD
)/LOCK XADD
иSTORE
- разрешить следующее переупорядочение:
STORE-A(seq_cst);
LOAD-C(acq_rel);
можно переупорядочить до LOAD-C(acq_rel);
STORE-A(seq_cst);
- На PowerPC
STORE-A(seq_cst);
LOAD-C(relaxed);
можно переупорядочить до LOAD-C(relaxed);
STORE-A(seq_cst);
Позволяет переупорядочить хранилище (Таблица 5 - PowerPC): http://www.rdrop.com/users/paulmck/scalability/paper/whymb.2010.06.07c.pdf
Сохраненные после загрузки нагрузки
т.е. Код PowerPC:
STORE-A(seq_cst);
STORE-C(relaxed);
LOAD-C(relaxed);
LOAD-B(seq_cst);
Можно изменить порядок:
LOAD-C(relaxed);
STORE-A(seq_cst);
STORE-C(relaxed);
LOAD-B(seq_cst);
PowerPC - GCC 4.8: https://godbolt.org/g/xowFD3
С++ и asm-code:
#include <atomic>
// Atomic load-store
void test() {
std::atomic<int> a, b, c; // addr: 20, 24, 28
a.store(2, std::memory_order_seq_cst); // li r9<-2; sync; stw r9->[a];
c.store(4, std::memory_order_relaxed); // li r9<-4; stw r9->[c];
c.load(std::memory_order_relaxed); // lwz r9<-[c];
int tmp = b.load(std::memory_order_seq_cst); // sync; lwz r9<-[b]; ... isync;
}
Разделив a.store
на две части - его можно изменить следующим образом:
#include <atomic>
// Atomic load-store
void test() {
std::atomic<int> a, b, c; // addr: 20, 24, 28
//a.store(2, std::memory_order_seq_cst); // part-1: li r9<-2; sync;
c.load(std::memory_order_relaxed); // lwz r9<-[c];
a.store(2, std::memory_order_seq_cst); // part-2: stw r9->[a];
c.store(4, std::memory_order_relaxed); // li r9<-4; stw r9->[c];
int tmp = b.load(std::memory_order_seq_cst); // sync; lwz r9<-[b]; ... isync;
}
Где load-from-memory lwz r9<-[c];
выполняется раньше, чем память-память stw r9->[a];
.
Также на PowerPC может быть такое переупорядочение:
STORE-A(seq_cst);
STORE-C(relaxed);
может быть изменен на STORE-C(relaxed);
STORE-A(seq_cst);
Поскольку PowerPC имеет слабую модель упорядочения памяти - позволяет переупорядочить хранилище Store Store (Таблица 5 - PowerPC): http://www.rdrop.com/users/paulmck/scalability/paper/whymb.2010.06.07c.pdf
Магазины, упорядоченные после магазинов
т.е. на операциях PowerPC. Магазин можно переупорядочить с помощью другого Хранилища, тогда предыдущий пример может быть переупорядочен, например:
#include <atomic>
// Atomic load-store
void test() {
std::atomic<int> a, b, c; // addr: 20, 24, 28
//a.store(2, std::memory_order_seq_cst); // part-1: li r9<-2; sync;
c.load(std::memory_order_relaxed); // lwz r9<-[c];
c.store(4, std::memory_order_relaxed); // li r9<-4; stw r9->[c];
a.store(2, std::memory_order_seq_cst); // part-2: stw r9->[a];
int tmp = b.load(std::memory_order_seq_cst); // sync; lwz r9<-[b]; ... isync;
}
Где хранится память stw r9->[c];
выполняется раньше, чем память-память stw r9->[a];
.