如何原子地否定 std::atomic_bool

How to atomically negate an std::atomic_bool?

本文关键字:atomic bool std 何原      更新时间:2023-10-16

朴素布尔否定

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次。

编辑:对于那些想要运行此代码的人,我在 ubuntu 4.7 上使用具有两个内核的 GCC 11.10 快照。编译选项包括:

-std=c++0x -Wall -pedantic-errors -pthread
b = !b不是原子的,

因为在C++源中,您有一个b的原子纯读取(相当于b.load(),然后是b的单独原子赋值(相当于b.store()(。

在C++抽象机器中,没有任何内容可以将整个组合转换为原子 RMW 操作,并且没有将任意操作组合为原子 RMW 操作的语法(除了将其放入 CAS 重试循环(。


有两个选项可以使用:

  1. 代替atomic<bool>,使用整型(例如 atomic<int>atomic<unsigned char> (,可以是 0 或 1,也可以是 1 或 xor :

    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 重试循环在低争用的情况下很好。 在最坏的情况下,这不是无等待的,而是无锁的,因为每次重试时,至少有一个线程都会取得进展。 (实际上,硬件仲裁更为复杂,内核甚至可以访问缓存行进行尝试。

    如果可能存在高争用,则首选允许您使用原子异或的整数版本。