可以使用松弛的记忆顺序来观察条件吗?

Can relaxed memory order be used to observe a condition?

std::atomic<bool> b;

void f()
{
    // block A
    if(b.load(std::memory_order_relaxed))
    {
        // block B
    }
    // block C
}

void g()
{
    // block B
    b.store(true, std::memory_order_release);
}

理论上,只有当原子加载 returns 为真,
时才应执行块 B 但是 B 块的一部分是否有可能在加载之前重新排序? store 具有释放内存顺序保证块 B 上的所有操作都是可见的副作用,但是如果 load 是宽松操作,这是否仍然适用?

您应该关心的主要事情是访问您使用此 "mutex" 锁定的资源。如果没有 acquire/release 语义,您的线程可能看不到其他线程对该资源所做的更改。也就是说,您对该数据的读取和另一个线程对其的写入构成了没有 acquire/release 语义的数据竞争。

如果您只想访问原子值本身,而不任何问题与此相关的世界上正在发生的事情,则您应该只使用宽松的内存顺序原子的价值。

Intel 在 Benefitting Power and Performance Sleep Loops:

中建议在尝试锁定之前进行放松加载
ATTEMPT_AGAIN:
    if (!acquire_lock())
    {
        /* Spin on pause max_spin_count times before backing off to sleep */
        for(int j = 0; j < max_spin_count; ++j)
        {
            /* pause intrinsic */
            _mm_pause();
            if (read_volatile_lock()) // <--- relaxed load
            {
                if (acquire_lock())
                {
                    goto PROTECTED_CODE;
                }
            }
        }
        /* Pause loop didn't work, sleep now */
        Sleep(0);
        goto ATTEMPT_AGAIN;
    }
PROTECTED_CODE:
    get_work();
    release_lock();
    do_work();

acquire_lock 使用 acquire sematics 以便松弛负载不会在 acquire_lock.

之后重新排序

但是请注意,它会先尝试无条件锁定,然后再执行 busy-wait 循环以放松负载。

您的示例中有两个 block B。我说的是 void f() 加载函数中的那个。

is it possible that part of block B can get reordered before the load?

是的。编译器可以从 if() 主体中提升负载并在 b.load 之前执行它们。如果块 B 和块 C 读取相同的 non-atomic 变量,则很可能会发生这种情况。

并且即使没有 compile-time 重新排序,也有 real-life 机制可以创建此重新排序:

具体来说,分支推测(即分支预测+out-of-order推测执行)会让CPU在[=14=之前开始执行block B ] 甚至开始。

您不能依赖 "causality" 或 "it would have to know the b.load() result before it can know what to execute next" 等任何推理。

或者如果块 B 中没有任何存储,编译器可能会将 if() 的 if-conversion 转换为无分支代码。然后它可以很明显地使用 non-atomic 重新排序块 B 和 C 中的负载或其他松弛或获取负载。

(记住 acq/rel 是 one-way 障碍。)


这样的推理(基于真正的编译器和 CPU 可以做的事情)可以用来证明某些东西 安全。 但要小心走另一条路:基于 "safe on the compiler I know about" 的推理并不总是意味着 "safe in portable ISO C++".

有时 "safe on the compiler I know about" 或多或少就足够了,但很难将其与 "happens to work on the compile I know about" 区分开来,后者的未来编译器版本或看似无关的源代码更改可能会破坏某些内容。

因此,请始终尝试根据 C++ 内存模型以及它如何为您关心的 ISA(例如 strongly-ordered x86)高效编译来推断内存顺序。就像您可能会看到 relaxed 将允许 compile-time 重新排序,这对您的情况实际上很有用。