Я борюсь с разделом 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»,
- который «происходит между потоками до» атомарного чтения / получения в потоке «2» (когда он читает «истина»),
- что, в свою очередь, «упорядочено до» и, следовательно, «происходит до» (не атомарных) операций чтения и записи (в потоке «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 и даст ожидаемый результат.
На x86 все обычные (не временные) хранилища имеют семантику выпуска. Руководство разработчика программного обеспечения для архитектур Intel® 64 и IA-32, том 3 (3A, 3B, 3C и 3D): Руководство по системному программированию, 8.2.3.3 Stores Are Not Reordered With Earlier Loads. Итак, ваш компилятор правильно переводит ваш код (как это удивительно), так что ваш код фактически является полностью последовательным, и одновременно ничего интересного не происходит. — person Chris Hall schedule 09.02.2020
Спасибо ! (Я тихонько сходил с ума.) FWIW Я рекомендую ссылку — в частности, раздел 3, Модель программиста. Но чтобы избежать ошибки, в которую я попал, обратите внимание, что в 3.1 Абстрактная машина есть аппаратные потоки, каждый из которых является одним упорядоченным потоком выполнения инструкций (выделено мной). Теперь я могу вернуться к попыткам понять стандарт C11 … с меньшим когнитивным диссонансом 🙂 — person Chris Hall schedule 10.02.2020
