Как атомарно отрицать std :: atomic_bool? - PullRequest
39 голосов
/ 21 марта 2012

Наивное логическое отрицание

std::atomic_bool b;
b = !b;

не кажется атомарным.Я подозреваю, что это потому, что operator! запускает приведение к обычному bool.Как атомно выполнить эквивалентное отрицание?Следующий код иллюстрирует, что наивное отрицание не является атомарным:

#include <thread>
#include <vector>
#include <atomic>
#include <iostream>

typedef std::atomic_bool Bool;

void flipAHundredThousandTimes(Bool& foo) {
  for (size_t i = 0; i < 100000; ++i) {
    foo = !foo;
  }
}

// Launch nThreads std::threads. Each thread calls flipAHundredThousandTimes 
// on the same boolean
void launchThreads(Bool& foo, size_t nThreads) {

  std::vector<std::thread> threads;
  for (size_t i = 0; i < nThreads; ++i) {
    threads.emplace_back(flipAHundredThousandTimes, std::ref(foo));
  }

  for (auto& thread : threads) thread.join();

}

int main() {

  std::cout << std::boolalpha;
  Bool foo{true};

  // launch and join 10 threads, 20 times.
  for (int i = 0; i < 20; ++i) {
    launchThreads(foo, 10);
    std::cout << "Result (should be true): " << foo << "\n";
  }

}

Код запускает 10 потоков, каждый из которых переворачивает atomic_bool на большое число, даже число раз (100000), и распечатываетлогическое значение.Это повторяется 20 раз.

РЕДАКТИРОВАТЬ : Для тех, кто хочет запустить этот код, я использую снимок GCC 4.7 в Ubuntu 11.10 с двумя ядрами.Параметры компиляции:

-std=c++0x -Wall -pedantic-errors -pthread

1 Ответ

28 голосов
/ 21 марта 2012

b = !b не является атомарным, потому что в источнике C ++ атомное чистое чтение равно b (эквивалентно b.load(), а затем отдельно-атомное присвоение b (эквивалентно b.store()).

Ничто не превращает всю комбинацию в атомарную операцию RMW в абстрактной машине C ++, и нет никакого синтаксиса для объединения произвольных операций в атомарные операции RMW (кроме помещения его в цикл повторения CAS).


Можно использовать две опции:

  1. Вместо atomic<bool> используйте целочисленный тип (например, atomic<int> или atomic<unsigned char>), который может быть 0 или 1и с помощью 1:

    std::atomic<int> flag(0);
    
    flag ^= 1;        //equivalent to flag.fetch_xor(1);
    

    К сожалению, fetch_xor не предоставляется для atomic<bool>, только для целочисленных типов.

  2. Выполните сравнение /операция обмена в цикле, пока она не завершится успешно:

    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.

...