Adve and Gharachorloo's report, на фиг.4b, приводится следующий пример программы, которая проявляет неожиданное поведение в отсутствие последовательной последовательности:Можно ли использовать C11 заборы для рассуждений о записи из других потоков?
Мой вопрос, можно ли, используя только C11 заборы и memory_order_relaxed
грузы и хранилища, чтобы гарантировать, что регистр1, если он написан, будет записан со значением 1. Причина, по которой в абстрактной форме может быть трудно гарантировать, что P1, P2 и P3 могут находиться в разных точках в патологической сети NUMA с свойство, которое P2 видит в записи P1 до того, как P3 делает, но почему-то P3 видит запись P2 очень быстро. Причина, по которой это может быть трудно гарантировать в отношении спецификации C11, в частности, заключается в том, что запись P1 в A и P2, прочитанные в A, не синхронизируются друг с другом, и поэтому в соответствии с пунктом 5.1.2.4.26 спецификации результат приведет к неопределенному поведению , Возможно, я могу обойти неопределенное поведение через расслабленный атомный выбор/сохранение, но я до сих пор не знаю, как разумно рассуждать о порядке, рассматриваемом P3.
Ниже приведено MWE, пытающееся решить проблему с забором, но я не уверен, что это правильно. Я особенно обеспокоен тем, что забор релиза недостаточно хорош, потому что он не будет сбрасывать буфер хранения p1, а только p2. Тем не менее, он ответит на мой вопрос, если вы можете утверждать, что утверждение никогда не потерпит неудачу только на основе стандарта C11 (в отличие от какой-либо другой информации, которая может иметь о конкретном компиляторе и архитектуре).
#include <assert.h>
#include <stdatomic.h>
#include <stddef.h>
#include <threads.h>
atomic_int a = ATOMIC_VAR_INIT(0);
atomic_int b = ATOMIC_VAR_INIT(0);
void
p1(void *_ignored)
{
atomic_store_explicit(&a, 1, memory_order_relaxed);
}
void
p2(void *_ignored)
{
if (atomic_load_explicit(&a, memory_order_relaxed)) {
atomic_thread_fence(memory_order_release); // not good enough?
atomic_store_explicit(&b, 1, memory_order_relaxed);
}
}
void
p3(void *_ignored)
{
int register1 = 1;
if (atomic_load_explicit(&b, memory_order_relaxed)) {
atomic_thread_fence(memory_order_acquire);
register1 = atomic_load_explicit(&a, memory_order_relaxed);
}
assert(register1 != 0);
}
int
main()
{
thrd_t t1, t2, t2;
thrd_create(&t1, p1, NULL);
thrd_create(&t2, p2, NULL);
thrd_create(&t3, p3, NULL);
thrd_join(&t1, NULL);
thrd_join(&t2, NULL);
thrd_join(&t3, NULL);
}
Спасибо за недостающий забор. Можете ли вы сделать свой аргумент на основе стандартного текста C11? Как минимум, для этого требуется, чтобы a) что две конфликтующие операции 'memory_order_relaxed' не создают расы данных (и, следовательно, неопределенное поведение), и b) что, как вы говорите, спецификация C11 гарантирует согласованность чтения. – user3188445
Я обновил свой ответ со ссылкой на стандарт. – Tsyvarev
Спасибо за рекомендации. К сожалению, я до сих пор не могу связать свою вторую цитату с моим вопросом. Здесь M соответствует b здесь, тогда как A и X соответствуют записи p2 из b, а B соответствует чтению p3 для b? Тогда почему-то для того, чтобы язык был релевантным, вам нужно утверждать, что A происходит до B. Кажется интуитивным, учитывая, что B наблюдал побочный эффект A, но A должен был бы inter-thread произойти до B. Как вы утверждаете это? Синхронизируется ли A с B (и если да, то каким аргументом)? Или это доступ к синхронизируемой по отношению друг к другу? – user3188445