Это правильный способ атомарного чтения и записи bool?

Булевский флаг переключается двумя потоками. Имеет ли смысл следующий код?

static bool ATOMIC_BOOL_READ( volatile bool& var )
{
    return __sync_fetch_and_or(&var, 0);
}

static void ATOMIC_BOOL_WRITE(volatile bool& var, bool newval )
{
    __sync_bool_compare_and_swap( &var, !newval, newval);
}

Обратите внимание на несколько вещей:

  • Я передаю ссылку bool. Имеют смысл?

  • Для удовольствия, я также объявляю его изменчивым.

  • Функции статические

Update:

Основной вопрос, который я хочу задать: В чем разница между атомарностью и барьером памяти? Если Thread A выполняет атом, встроенный в переменную foo, то Thread B не может делать НИЖЕ на переменной foo; следовательно, создает барьер памяти?

Ответ 1

Атомы по сути являются не переносимыми, и это расширения GCC, которые могут отсутствовать в будущем и не будут работать с другими компиляторами.

Вы должны прочитать остальную часть ответа только после того, как полностью поняли приведенное выше утверждение.

Примечательным фактом является то, что все существующие машины всегда гарантировали, что доступ к данным до определенного размера является атомарным. Это объясняется тем, что данные в памяти и в системной шине передаются с определенной детализацией. Логическое значение должно быть атомарным в большинстве машин, поэтому:

bool ATOMIC_BOOL_READ(volatile bool* b) {
    bool v = *b;
    __sync_synchronize(); // ensure value pushed to memory
    return v;
}

void ATOMIC_BOOL_WRITE(volatile bool* b, bool v) {
    __sync_synchronize(); // read will return fresh value
   *b = v;
}

Вероятно, это причина, по которой GCC не обеспечивает простые загрузки/хранения специальных атомных операций: они уже должны быть атомарными.

Ответ 2

Вам нужна только атомика для семейства действий read-modify-write. Изолированные чтения и записи уже являются атомарными.

В вашем вопросе говорится, что два потока "переключают" один и тот же bool. Это не то, что делают ваши опубликованные функции - если вы объединяете эти функции для выполнения переключения, он по-прежнему не является потокобезопасным.

Почему бы не использовать std::atomic_int?

i=0; является потокобезопасным, i=i+1; не потому, что если другой поток выполняет одно и то же в одно и то же время, i может быть увеличен только один раз вместо двух. Это команда read-modify-write и примерная последовательность задач (read1,read2,modify1,write1,modify2,write2) для потоков 1 и 2. До сих пор это стандартное.

Теперь вы можете понять, почему это также не является потокобезопасным?

bool x = ATOMIC_BOOL_READ (&b);
x = !x;
ATOMIC_BOOL_WRITE (&b, x);

В ваших функциях добавлена ​​безопасность нет. Вы можете написать функцию

bool atomic_toggle_and_return_new_value (bool * b) { ... }

на основе сравнения и замены или тестирования и набора, скажем. Для более сложных случаев, когда "оба потока читаются и записываются в один и тот же bool, вам необходимо, чтобы читатели и писатели совместно синхронизировались в каком-то критическом разделе (или смотрели на блокировку и безжизненные алгоритмы).

Ответ 3

__sync_bool_compare_and_swap является правильным, но, возможно, намного дороже, чем необходимо.

Это зависит от того, что вам нужно. __sync_lock_test_and_set будет дешевле (и гарантированно будет атомарным), но он не отчитается, если операция была "успешной", поскольку значение было тем, что ожидалось (оно всегда "успешно", и вы возвращаете значение обратно тоже, это просто не сработает, если это не то, что вы сказали). Однако это некоторая информация, которая не всегда интересна.

Вместо атомных встроенных функций вы можете использовать std::atomic<bool>, если вы скомпилируете в режиме С++ 0x, который предлагает .load() и .store(). Эти функции, возможно, более эффективны (либо использование знаний о том, что какая-либо операция является атомарной, так и вставка барьеров или использование специальных операций или что-то еще), а ваш код более переносимый (и более очевидный).

Кроме того, по почти каждой архитектуре вы также можете ожидать (думая, что нет никакой гарантии!) писать в bool, будучи атомарным в любом случае.

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