如果必须检查溢出或有条件地执行操作,std::atomic 是否是多余的?

use*_*358 4 c++ atomic overflow c++11 stdatomic

例如,您可以安全地递增和递减 std::atomic_int 。但是,如果您需要检查溢出或根据该值有条件地执行某些例程,那么无论如何都需要锁。由于您必须比较该值,并且在比较成功后线程可能会被交换,另一个线程会修改,...bug。

但如果你需要锁,那么你可以使用普通整数而不是原子。我对吗?

Mik*_*ine 5

不,您仍然可以有条件地使用 std::atomic 。

首先, for 的溢出行为.fetch_add被明确定义为原子类型的无符号或2 的补码环绕(尽管可能不是您想要的)。与非原子类型不同,整数溢出仍然是 UB。

你是对的foo++,然后单独读取foo将是一个错误,因此请int tmp = foo++;使用原子 RMW 操作的返回值。

如果您绝对必须检查溢出,或以其他方式有条件地执行操作,则可以使用比较交换。这使您可以读取该值,决定是否要对其进行处理,然后自动更新该值(如果它没有更改)。这里的关键部分是系统会告诉你原子更新是否失败,在这种情况下你可以返回到开始并读取新值并再次做出决定。

举个例子,如果我们只想将原子整数的最大值设置为 4(例如,在某种引用计数中),我们可以这样做:

#include <atomic>

static std::atomic<int> refcount = 0;

int val = refcount; // this doesn't need to be in the loop as on failure compare-exchange-strong updates it
while(true)
{
   if(val == 4)
   {
       // there's already 4 refs here, maybe come back later?
       break;
   }

   int toChangeTo = val + 1;
   if(refcount.compare_exchange_weak(val, toChangeTo))
   {
       // we successfully took a ref!
       break;
   }

    // if we fail here, another thread updated the value whilst we were running, just loop back and try again
}
Run Code Online (Sandbox Code Playgroud)

像这样的循环通常写成do{...}while(!refcount.compare_exchange_weak(...));

我们使用compare_exchange_weak而不是compare_exchange_strong. 有时这可能会错误地失败,因此您需要循环执行此操作。然而,无论如何我们都有一个循环(一般来说,当您需要处理真正的故障时,您总是会这样做),因此compare_exchange_weak这里很有意义。

在某些 ISA 上,需要使用 asm 循环来实现,compare_exchange_strong但不需要weak;在 x86 等其他 ISA 上,两者都编译为相同的 asm。weak只要为下一次尝试做准备的代码很便宜,就像在本例中一样,使用它不会让任何事情变得更糟compare_exchange_weak

  • 这不是使用 `.compare_exchange_weak` 的原型循环吗? (2认同)