2015-12-30 3 views
1

Я разрабатываю многопоточный алгоритм, в котором требование состоит в том, чтобы прочитать последнее значение общей переменной. Запись в переменную является атомарной (с использованием сравнения и замены). Однако чтения не являются атомарными.атомные записи и изменчивые чтения

Рассмотрим следующий пример:

//Global variable 
int a = 10; 


// Thread T1 
void func_1() { 
    __sync_bool_compare_and_swap(&a, 10, 100); 
} 

// Thread T2 
void func_2() { 
    int c = a; 
    /* Some Operations */ 
    int b = a; 
    /* Some Operations */ 
} 

Если код int b = a выполняется (по Thread Т2) после того, как __sync_bool_compare_and_swap в func_1 (по нити T1), то согласно моему пониманию, он все еще не гарантировано читать последнее значение «variable a», поскольку компилятор может кэшировать «a» и использовать старое значение «a».

Теперь, чтобы избежать этой проблемы, я объявил переменную «летучий», как показано ниже:

volatile int a = 10; 

// Thread T1 
void func_1() { 
    __sync_bool_compare_and_swap(&a, 10, 100); 
} 

// Thread T2 
void func_2() { 
    volatile int c = a; 
    /* Some Operations */ 
    volatile int b = a; 
    /* Some Operations */ 
} 

Для того же сценария выполнения int b = a посредством резьбы T2 после __sync_bool_compare_and_swap ниткой Т1 закончится, он гарантированно прочитайте последнее значение «a»?

Каким образом согласованность когерентности и модель согласованности памяти влияют на изменчивое чтение после атомной записи?

+1

Можете ли вы использовать C++ 11 и 'std :: atomic '? – Jarod42

+0

Взгляните на: http://lxr.oss.org.cn/source/Documentation/memory-barriers.txt – Malkocoglu

+1

@ Jarod42 Использование std :: atominc решит проблему, но я пытаюсь понять, что произойдет, если волатильное чтение следует за атомной записью. –

ответ

0

На всех платформах, которые вы, скорее всего, будете использовать поддержку C++ и несколько потоков, чтение из нестабильной, выровненной int будет атомарным и будет читать последнее значение. Однако это не гарантируется стандартом C++. Там может быть какая-то платформа, где она не работает, и она может не работать со следующим процессором, версией компилятора или версией ОС.

В идеале используйте то, что гарантированно обеспечит атомарность и видимость. C++ - 11 атома, вероятно, лучший выбор. Соблюдение компилятора будет следующим лучшим выбором. Если у вас нет другого выбора, кроме как использовать volatile, я бы предложил вам использовать тесты препроцессора, чтобы подтвердить, что вы находитесь на платформе, где, как известно, достаточно, и выдает ошибку (с #error), если нет.

Обратите внимание, что на каждой платформе, которую вы, скорее всего, будете использовать, кэши памяти процессора совершенно неактуальны, потому что они становятся невидимыми из-за согласованности аппаратного кеша. На всех платформах, которые вы, скорее всего, будете использовать, проблемы - это только оптимизация компилятора, предварительные чтения и опубликованные записи.

2

Ключевое слово volatile гарантирует, что компилятор не сохранит переменную в регистре и вместо этого будет загружать переменную из памяти каждый раз, когда она будет использоваться. Он не имеет ничего общего с моделями согласованности кеша или памяти системы, в которой он работает.

+0

Как насчет «чтения последнего значения». Выполнение 'int b = a' после' __sync_bool_compare_and_swap', будет 'b' иметь значение' 10 или 100'. –

+0

Кроме того, если память кэширована, переменная volatile будет считываться из кэшированной памяти, а не напрямую поступать в основную память. Таким образом, волатильное чтение одним потоком после атомарного записи другим потоком, как другие кеши видят значение. –

+0

Скорее всего, если ваши данные не хранятся в области памяти, которая требует специального управления/синхронизации между процессами/потоками. – Turn

0

volatile не делает операции чтения атомарными. Неатомные считывания, совпадающие с (атомарным) записью, приводят к неопределенному поведению. Используйте атомное считывание в любой форме, либо std::atomic, либо свойства. Не используйте volatile для любой формы параллелизма.

Атомное считывание само по себе не гарантирует, что значение будет последние. В вашем случае нить T2 может никогда не читать 100, теоретически. В стандарте говорится, что реализация (аппаратное обеспечение, ОС и т. Д.) Должна приложить максимум усилий, чтобы сделать записи видимыми для других потоков за конечное время. Возможно, здесь невозможно предъявить формальные требования.

С дополнительной синхронизацией вы можете достичь более ограниченного поведения:

std::atomic<int> a = 10; 
std::atomic<bool> done = false; 

void func_1() { 
    int old = 10; 
    if (a.compare_exchange_strong(old, 100)) 
     done.store(true); 
} 

void func_2() { 
    bool is_done = done.load(); 
    int b = a.load(); 
    assert(b == 100 || !is_done); 

    while (!done.load()); // May spin indefinitely long, but should not do that 
    assert(a.load() == 100); 
} 

На самом деле, чтобы поймать, что просто атомное чтения читает не последнего значения, можно было бы поставить достаточно синхронизации в программу (для определения последние), поэтому он будет работать правильно.

+0

«Атомное считывание само по себе не гарантирует, что значение будет последним». Здесь я говорю о очень специфическом чередовании, которое 'volatile int b = a' выполняется после' __sync_bool_compare_and_swap'. Я согласен, что если это чередование не произойдет, мы можем не увидеть последнее значение. Поэтому в этом конкретном чередовании атомы должны работать правильно? –

+0

Если вы каким-то образом проверяете или принудительно выполняете _executes после_ (стандарт использует слова _happens after_) между этими двумя операциями, тогда значение, считанное атомарно, будет тем, что было написано ранее. –

Смежные вопросы