атомарные записи и непостоянные чтения

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

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

//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 выполняется (потоком T2) после __sync_bool_compare_and_swap в func_1 (потоком T1), то, как я понимаю, по-прежнему не гарантируется чтение последнего значения «переменной a», поскольку компилятор может кэшировать «a» и используйте старое значение «а».

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

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 потоком T1, гарантировано ли чтение последнего значения «a»?

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


person Varun V    schedule 30.12.2015    source источник
comment
Можете ли вы использовать c ++ 11 и std::atomic<int>?   -  person Jarod42    schedule 30.12.2015
comment
Взгляните на: lxr.oss.org.cn/source/Documentation /memory-barriers.txt   -  person Malkocoglu    schedule 30.12.2015
comment
@ Jarod42 Использование std :: atominc ‹int› решит проблему, но я пытаюсь понять, что произойдет, если за атомарной записью последует изменчивое чтение.   -  person Varun V    schedule 30.12.2015
comment
volatile int b = a; не безопаснее, чем int b = a;. Другой поток все равно не будет изменять b (копию a). Проблема может появиться в назначении, и volatile int a не решает ее (но проблема может появляться реже).   -  person Jarod42    schedule 30.12.2015


Ответы (3)


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

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

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

person David Schwartz    schedule 31.12.2015

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

person Turn    schedule 30.12.2015
comment
Как насчет чтения последнего значения. Выполнение int b = a после __sync_bool_compare_and_swap будет b иметь значение 10 or 100. - person Varun V; 30.12.2015
comment
Кроме того, если память кэшируется, изменчивая переменная будет считываться из кэшированной памяти, а не напрямую в основную память. Итак, изменчивое значение, прочитанное одним потоком после атомарной записи другим потоком, как другие кеши видят значение. - person Varun V; 30.12.2015
comment
Скорее всего, если ваши данные не хранятся в области памяти, которая требует специального управления / синхронизации между процессами / потоками. - person Turn; 30.12.2015
comment
Я не совсем понял, что здесь вероятно? Видите старое значение или новое значение? - person Varun V; 30.12.2015
comment
Невозможно ответить на ваши вопросы, не зная больше о системе, о которой вы говорите. Если вы просто говорите об общих процессорах, подобных тем, что используются в вашем компьютере, тогда оборудование / ОС обрабатывает согласованность кеша за вас, и вам не нужно об этом беспокоиться. Для чего-то более сложного ответ может быть более сложным. - person Turn; 30.12.2015
comment
Базовая система - это обычный ЦП с процессорами Intel i7 / Xeon, работающими в ОС RHEL / Ubuntu / Windows. - person Varun V; 30.12.2015
comment
@VarunV Кеши сделаны согласованными аппаратно. Вставьте протокол MESI в свою любимую поисковую систему. - person David Schwartz; 31.12.2015

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);
}

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

person Constantin Baranov    schedule 31.12.2015
comment
Само по себе атомарное чтение не гарантирует, что значение будет последним. Здесь я говорю об очень специфическом чередовании, которое volatile int b = a выполняется после __sync_bool_compare_and_swap. Я согласен с тем, что если это чередование не произойдет, мы не сможем увидеть последнее значение. Следовательно, в этом конкретном чередовании атомарный должен работать правильно? - person Varun V; 01.01.2016
comment
Если вы каким-то образом проверяете или применяете выполняется после (в стандарте используются слова происходит после) между этими двумя операциями, тогда значение, прочитанное атомарно, будет тем, что было написано ранее. - person Constantin Baranov; 01.01.2016