7

我正在考虑原子变量是否可以在获取-释放对中加载旧值。假设我们有原子变量 x,我们用释放语义存储该变量,然后用获取语义加载它,理论上可以读取旧值吗?

std::atomic<int> x = 0;

void thread_1()
{
   x.store(1, std::memory_order_release);
}
void thread_2()
{
   assert(x.load(std::memory_order_acquire) != 0);
}

如果函数线程 1 在线程 2 加载 x 时完成(因此存储了新值),线程 2 是否可以从 x 加载旧值?换句话说,如果在加载之前完成了对 x 的实际存储,那么断言是否可以触发?

据我从互联网上的文章中了解到,这是可能的,但我不明白为什么。store to x 生成的内存栅栏保证清空存储缓冲区,而从 x 加载时获取内存栅栏保证使缓存行无效,因此它必须读取最新值。

添加

这是否意味着获取释放本身没有任何强制排序?只有在发布之前完成的任何事情都会在发布之前发生,而在获取之后完成的所有事情都会在它之后发生,因此获取-发布对强制对其他操作进行排序(为什么??)。我做对了吗?这是否意味着在下面的代码中断言保证不会触发

std::atomic<int> x = 0;
std::atomic<int> y = 0;

void thread_1()
{
   y.store(1, std::memory_order_relaxed);
   x.store(1, std::memory_order_release);
}
void thread_2()
{
   x.load(std::memory_order_acquire);
   assert(y.load(std::memory_order_relaxed) != 0);
}

当然,如果线程 1 已经完成了商店。如果我们用 while (x.load() == 0) 替换 x.load,这将 100% 有效,但我不知道是什么原因导致它起作用。

如果我用下面的代码替换代码怎么办

std::atomic<int> x = 0;

void thread_1()
{
   x.exchange(1, std::memory_order_acq_rel);
}
void thread_2()
{
   assert(x.exchange(0, std::memory_order_acq_rel) != 0);
}

它有什么改变吗?

谢谢。

4

1 回答 1

5

您可以将具有释放/获取内存顺序的存储/加载函数视为以下伪代码:

template<class T>
struct weak_atomic
{
   void store(T newValue)
   {
      ReleaseBarrier();
      m_value = newValue;
   }

   T load()
   {
      T value = m_value;
      AcquireBarrier();
      return value;      
   }

   volatile T m_value;
}

你说

存储到 x 保证清空存储缓冲区生成的内存栅栏

据我了解,释放内存屏障将导致 CPU 刷新其存储缓冲区,但它将在将新值应用于 x之前完成。因此,似乎可以通过另一个 CPU 从 x 读取旧值。

无论如何,弱原子是非常复杂的领域。在继续进行无锁编程之前,请确保您了解内存屏障。

添加

看来您仍然对内存障碍感到困惑。这是它们用法的一个非常常见的例子。

volatile int  x;
volatile bool ok;

void thread_1()
{
   x = 100;
   ok = true;
}

void thread_2()
{
   if (ok)
   {
      assert(x == 100);
   }
}

由于乱序执行,您可能会得到以下序列:

thread 1 sets ok to true
thread 2 checks ok is true and reads some garbage from x
thread 1 sets x to 100 but it is too late

另一个可能的顺序:

thread 2 reads some garbage from x
thread 2 checks for ok value

我们可以通过释放和获取内存屏障来解决这个问题。

volatile int  x;
volatile bool ok;

void thread_1()
{
   x = 100;
   ReleaseBarrier();
   ok = true;
}

void thread_2()
{
   if (ok)
   {
      AcquireBarrier();
      assert(x == 100);
   }
}

ReleaseBarrier()保证内存写入不会越过障碍。这意味着oktruex已包含有效值时才设置为。

AcquireBarrier()保证内存读取不会越过障碍。这意味着只有在检查状态x后才会读取的值。ok

这就是释放/获取对的使用方式。我们可以用我的weak_atomic.

volatile int  x;
weak_atomic<bool> ok;

void thread_1()
{
   x = 100;
   ok.store(true);
}

void thread_2()
{
   if (ok.load())
   {
      assert(x == 100);
   }
}
于 2010-12-14T19:48:40.513 回答