How to atomically negate an std::atomic_bool?

2019-03-08 23:52发布

问题:

The naive boolean negation

std::atomic_bool b;
b = !b;

does not seem to be atomic. I suspect this is because operator! triggers a cast to plain bool. How would one atomically perform the equivalent negation? The following code illustrates that the naive negation isn't atomic:

#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";
  }

}

The code launches 10 threads, each of which flips the atomic_bool a larrge, even, number of times (100000), and prints out the boolean. This is repeated 20 times.

EDIT: For those who want to run this code, I am using a GCC 4.7 snapshot on ubuntu 11.10 with two cores. The compilation options are:

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

回答1:

b = !b is not atomic because it is comprised of both a read and a write, each of which is an atomic operation.

There are two options to use:

  1. Instead of atomic<bool>, use an integral type (e.g. atomic<int>) which can be 0 or 1, and xor it with 1:

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

    Unfortunately, fetch_xor is not provided on atomic<bool>, only on integral types.

  2. Perform a compare/exchange operation in a loop, until it succeeds:

    std::atomic<bool> flag(false);
    
    bool oldValue = flag.load();
    while (!flag.compare_exchange_weak(oldValue, !oldValue)) {}