Ответ 1
b = !b
не является атомарным, потому что в источнике C++ вы читаете атомарное чтение b
(эквивалентно b.load()
, а затем отдельно атомарное присваивание b
(эквивалентно b.store()
).
Ничто не превращает всю комбинацию в атомарную операцию RMW в абстрактной машине C++, и нет никакого синтаксиса для объединения произвольных операций в атомарные операции RMW (кроме помещения его в цикл повторения CAS).
Есть два варианта использования:
-
Вместо
atomic<bool>
, используйте целочисленный тип (например,atomic<int>
илиatomic<unsigned char>
), который может быть 0 или 1, и xor его с 1:std::atomic<int> flag(0); flag ^= 1; //equivalent to flag.fetch_xor(1);
К сожалению,
fetch_xor
не предоставляется дляatomic<bool>
, только для целочисленных типов. -
Выполняйте операцию сравнения/обмена в цикле, пока она не завершится успешно:
std::atomic<bool> flag(false); bool oldValue = flag.load(); while (!flag.compare_exchange_weak(oldValue, !oldValue)) {}
К сожалению, компиляторы для x86 обычно не оптимизируют этот цикл в
lock xor byte [flag], 1
в asm; вы получите реальный цикл повторения cmpxchg. На практике повторные циклы cmpxchg хороши с низкой конкуренцией. В худшем случае это не без ожидания, но без блокировки, потому что по крайней мере один поток будет прогрессировать каждый раз, когда все они повторяются. (На практике это более сложно с аппаратным арбитражем, для которого ядро даже получает доступ к строке кэша, чтобы попытаться.)Если возможна высокая конкуренция, предпочтите целочисленную версию, которая позволяет использовать атомарный xor.