Даже для простого примера связи с двумя потоками я с трудом могу выразить это в стиле атома C11 и memory_fence, чтобы получить правильное упорядочение памяти:
общие данные:
volatile int flag, bucket;
поток производителя:
while (true) {
int value = producer_work();
while (atomic_load_explicit(&flag, memory_order_acquire))
; // busy wait
bucket = value;
atomic_store_explicit(&flag, 1, memory_order_release);
}
потребительский поток:
while (true) {
while (!atomic_load_explicit(&flag, memory_order_acquire))
; // busy wait
int data = bucket;
atomic_thread_fence(/* memory_order ??? */);
atomic_store_explicit(&flag, 0, memory_order_release);
consumer_work(data);
}
Насколько я понимаю, выше код будет правильно заказывать хранилище-в-ковке → флаг-store- > flag-load → load-from-bucket. Тем не менее, я думаю, что между нагрузкой и ведром остается состояние гонки и снова записывать ведро с новыми данными. Чтобы заставить порядок следовать за чтением в bucket, я думаю, мне понадобится явный atomic_thread_fence()
между прочитанным ведром и следующим атомарным_стопом. К сожалению, не существует аргумента memory_order
для обеспечения чего-либо в предыдущих нагрузках, даже memory_order_seq_cst
.
Действительно грязное решение может состоять в том, чтобы повторно назначить bucket
в потребительском потоке с фиктивным значением: это противоречит понятию потребительского чтения.
В старом мире C99/GCC я мог бы использовать традиционный __sync_synchronize()
, который, я считаю, был бы достаточно сильным.
Каким было бы более удобное решение C11 для синхронизации этой так называемой антизависимости?
(Конечно, я знаю, что лучше избегать такого низкоуровневого кодирования и использовать доступные конструкторы более высокого уровня, но я хотел бы понять...)