获取-释放对乱序执行

发布于 2024-10-07 18:13:34 字数 1276 浏览 9 评论 0原文

我正在考虑原子变量是否可以加载获取-释放对中的旧值。 假设我们有原子变量 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);
}

它会改变什么吗?

谢谢。

I'm thinking of whether or not it is possible for atomic variable to load the old value in acquire-release pair.
Let's suppose we have atomic variable x, and we store that variable with release semantics and later load it with acquire semantics is it possible in theory to read the old value?

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);
}

if function thread 1 is finished when thread 2 loads the x (so the new value is stored) is it possible for thread 2 to load old value from x? In other words if actual store to x is done before the load is it possible for assert to fire?

As far as I understood from articles in internet it is possible, but I cannot understand why. Memory fence generated by store to x guaranties to empty store buffer, while acquire memory fence in load from x is guaranteed to invalidate cache line, so it has to read up-to-date value.

added

Does it mean that acquire-release by itself doesn't have any enforced ordering? It's only anything that was done before release will happen before release and everything that is done after acquire will happen after it, so acquire-release pair enforces ordering on the other operations (why??). Did I get it right? Does it mean that in the code bellow assert is guaranteed to do not fire

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);
}

of course again if the thread 1 was already finished the store. If we replace x.load with while (x.load() == 0) this will 100% work, but I don't know what causes this to work.

And what if I replace the code with code bellow

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);
}

Does it change anything?

Thanks.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

作死小能手 2024-10-14 18:13:34

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

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() 保证内存写入不能跳过屏障。
这意味着仅当 x 已包含有效值时,ok 才会设置为 true

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

这就是释放/获取对的用途。我们可以用我的 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);
   }
}

You might consider store/load functions with release/acquire memory order as the following pseudo-code:

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

You said

Memory fence generated by store to x
guaranties to empty store buffer

As I understand, the release memory barrier will cause the CPU to flush its store buffer, but it will be done before applying new value to x. So, it seems possible to read old value from x by another CPU.

Anyway, weak atomics is very complex area. Make sure you understand memory barriers before proceeding with lock-free programming.

ADDED

It seems you are still confused with memory barriers. This is a pretty common example of their usage.

volatile int  x;
volatile bool ok;

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

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

Due to out-of-order execution you may get the following sequence:

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

Another possible sequence:

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

We may fix that with release and acquire memory barriers.

volatile int  x;
volatile bool ok;

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

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

ReleaseBarrier() guarantees that memory writes can't jump over the barrier.
It means that ok is only set to true when x already contains valid value.

AcquireBarrier() guarantees that memory reads can't jump over the barrier.
It means that the value of x is only read after checking ok state.

This is how release/acquire pair is intended to be used. We can rewrite this example with my 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);
   }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文