2015-12-09 14 views
2

Adve and Gharachorloo's report, на фиг.4b, приводится следующий пример программы, которая проявляет неожиданное поведение в отсутствие последовательной последовательности:Можно ли использовать C11 заборы для рассуждений о записи из других потоков?

enter image description here

Мой вопрос, можно ли, используя только 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); 
} 

ответ

2

memory_order_acquire Вы забываете забор в p3:

void 
p3(void *_ignored) 
{ 
    int register1 = 1; 
    if (atomic_load_explicit(&b, memory_order_relaxed)) { 
    atomic_thread_fence(memory_order_acquire); // <-- Here 
    register1 = atomic_load_explicit(&a, memory_order_relaxed); 
    } 
    assert(register1 != 0); 
} 

С этим забором, загружая a в p2 будет в происходит, прежде, чем связи с загрузкой a в p3.

C11 стандарт читать ГАРАНТИИ чтения согласованности, что означает, что нагрузка в p3 должны соблюдать или же-последующую модификацию, которая наблюдается при произошло до того, нагрузки в p2. Поскольку загрузка в p2 хранится в магазине p1, а затем возможны модификации a, при загрузке в p3 также следует наблюдать за хранением в p1.

Так что ваше утверждение никогда не может срабатывать.


Ссылки на соответсвующих заявления в стандартном:

5.1.2.4 стр.25: Выполнение программы содержит гонки данных, если он содержит две противоположные действия в разных потоках, по меньшей мере, один из которых равен , а не атомный, и ни один из них не происходит перед другим. Любая такая гонка данных приводит к неопределенному поведению.

Таким образом, атом доступ не может содержать гонку данных по определению.

5.1.2.4 стр.22: ... если вычисление значения А атомного объекта М происходит до вычисления значения B М, и значение, вычисленное A соответствует значению, запасенной побочным эффектом X , то величина вычисляется B должна либо равны значение, вычисленное с помощью A, или быть значением хранится побочным эффектом Y, где Y следует X в порядке модификации М.

Следующий пункт говорит, что это гарантия когерентности кеширования. Стандарт C++ 11 более конкретный, и говорит о когерентности чтения-чтения кеширования в аналогичной формулировке.

+0

Спасибо за недостающий забор. Можете ли вы сделать свой аргумент на основе стандартного текста C11? Как минимум, для этого требуется, чтобы a) что две конфликтующие операции 'memory_order_relaxed' не создают расы данных (и, следовательно, неопределенное поведение), и b) что, как вы говорите, спецификация C11 гарантирует согласованность чтения. – user3188445

+0

Я обновил свой ответ со ссылкой на стандарт. – Tsyvarev

+0

Спасибо за рекомендации. К сожалению, я до сих пор не могу связать свою вторую цитату с моим вопросом. Здесь M соответствует b здесь, тогда как A и X соответствуют записи p2 из b, а B соответствует чтению p3 для b? Тогда почему-то для того, чтобы язык был релевантным, вам нужно утверждать, что A происходит до B. Кажется интуитивным, учитывая, что B наблюдал побочный эффект A, но A должен был бы inter-thread произойти до B. Как вы утверждаете это? Синхронизируется ли A с B (и если да, то каким аргументом)? Или это доступ к синхронизируемой по отношению друг к другу? – user3188445