Я борюсь с разделом 5.1.2.4 стандарта C11, в частности с семантикой Release / Acquire. Я отмечаю, что https://preshing.com/20120913/acquire-and-release-semantics/ (среди прочих) заявляет, что:
... Семантика релиза предотвращает переупорядочение памяти релиз-релиза с любой предшествующей ему операцией чтения или записи в программном порядке.
Итак, для следующего:
typedef struct test_struct
{
_Atomic(bool) ready ;
int v1 ;
int v2 ;
} test_struct_t ;
extern void
test_init(test_struct_t* ts, int v1, int v2)
{
ts->v1 = v1 ;
ts->v2 = v2 ;
atomic_store_explicit(&ts->ready, false, memory_order_release) ;
}
extern int
test_thread_1(test_struct_t* ts, int v2)
{
int v1 ;
while (atomic_load_explicit(&ts->ready, memory_order_acquire)) ;
ts->v2 = v2 ; // expect read to happen before store/release
v1 = ts->v1 ; // expect write to happen before store/release
atomic_store_explicit(&ts->ready, true, memory_order_release) ;
return v1 ;
}
extern int
test_thread_2(test_struct_t* ts, int v1)
{
int v2 ;
while (!atomic_load_explicit(&ts->ready, memory_order_acquire)) ;
ts->v1 = v1 ;
v2 = ts->v2 ; // expect write to happen after store/release in thread "1"
atomic_store_explicit(&ts->ready, false, memory_order_release) ;
return v2 ;
}
где те выполнены:
> in the "main" thread: test_struct_t ts ;
> test_init(&ts, 1, 2) ;
> start thread "2" which does: r2 = test_thread_2(&ts, 3) ;
> start thread "1" which does: r1 = test_thread_1(&ts, 4) ;
Поэтому я ожидал бы, что поток "1" будет иметь r1 == 1, а поток "2" будет иметь r2 = 4.
Я ожидал бы этого, потому что (в соответствии с пунктами 16 и 18 раздела 5.1.2.4):
- все (не атомарные) операции чтения и записи «секвенируются до» и, следовательно, «происходят до» атомарной записи / выпуска в потоке «1»,
- который "inter-thread-случается-перед" атомарным чтением / приобретением в потоке "2" (когда это читает 'true'),
- который, в свою очередь, «секвенируется до» и, следовательно, «происходит до» (не атомарного) чтения и записи (в потоке «2»).
Однако вполне возможно, что я не смог понять стандарт.
Я заметил, что код, сгенерированный для x86_64, включает в себя:
test_thread_1:
movzbl (%rdi),%eax -- atomic_load_explicit(&ts->ready, memory_order_acquire)
test $0x1,%al
jne <test_thread_1> -- while is true
mov %esi,0x8(%rdi) -- (W1) ts->v2 = v2
mov 0x4(%rdi),%eax -- (R1) v1 = ts->v1
movb $0x1,(%rdi) -- (X1) atomic_store_explicit(&ts->ready, true, memory_order_release)
retq
test_thread_2:
movzbl (%rdi),%eax -- atomic_load_explicit(&ts->ready, memory_order_acquire)
test $0x1,%al
je <test_thread_2> -- while is false
mov %esi,0x4(%rdi) -- (W2) ts->v1 = v1
mov 0x8(%rdi),%eax -- (R2) v2 = ts->v2
movb $0x0,(%rdi) -- (X2) atomic_store_explicit(&ts->ready, false, memory_order_release)
retq
И при условии, что R1 и X1 происходят в таком порядке, это дает ожидаемый результат.
Но я понимаю x86_64 так, что чтение происходит по порядку с другими операциями чтения, а запись происходит по порядку с другими операциями записи, но операции чтения и записи могут не происходить друг с другом. Что подразумевает, что X1 может произойти раньше, чем R1, и даже X1, X2, W2, R1 произойдут в таком порядке - я полагаю. [Это кажется невероятно маловероятным, но если R1 были задержаны некоторыми проблемами с кешем?]
Пожалуйста: что я не понимаю?
Я отмечаю, что если я изменю загрузки / хранилища ts->ready
на memory_order_seq_cst
, код, сгенерированный для хранилищ:
xchg %cl,(%rdi)
что соответствует моему пониманию x86_64 и даст ожидаемый результат.
источник
8.2.3.3 Stores Are Not Reordered With Earlier Loads
. Таким образом, ваш компилятор правильно переводит ваш код (как это ни удивительно), так что ваш код фактически полностью последовательный, и ничего интересного не происходит одновременно.Ответы:
Модель памяти x86 в основном последовательная согласованность плюс буфер хранилища (с пересылкой хранилища). Так что каждый магазин - это релиз-магазин 1 . Вот почему только seq-cst магазины нуждаются в специальных инструкциях. ( C / C ++ 11 атомных отображений в asm ). Кроме того, https://stackoverflow.com/tags/x86/info содержит некоторые ссылки на документы x86, в том числе формальное описание модели памяти x86-TSO (в основном нечитаемо для большинства людей; требует просмотра множества определений).
Поскольку вы уже читаете превосходную серию статей Джеффа Прешинга, я укажу вам еще одну, которая более подробно рассматривается: https://preshing.com/20120930/weak-vs-strong-memory-models/
Единственное изменение порядка, которое разрешено в x86, - это StoreLoad, а не LoadStore , если мы говорим в этих терминах. (Переадресация магазина может сделать дополнительные забавные вещи, если загрузка только частично перекрывает хранилище; инструкции загрузки глобально невидимым , хотя вы никогда не получите это в сгенерированном компилятором коде для
stdatomic
.)@EOF прокомментировал правильную цитату из руководства Intel:
Сноска 1: игнорирование слабо упорядоченных магазинов NT; Вот почему вы обычно
sfence
после NT-магазинов. Реализации C11 / C ++ 11 предполагают, что вы не используете хранилища NT. Если да, используйте_mm_sfence
перед операцией выпуска, чтобы убедиться, что она соответствует вашим хранилищам NT. (Как правило , не используйте_mm_mfence
/_mm_sfence
в других случаях ; обычно вам нужно только заблокировать переупорядочение во время компиляции. Или, конечно, просто использовать stdatomic.)источник