易失性但不受限制的读数会产生无限期陈旧的价值吗?(在真实的硬件上)

Ste*_*Lin 28 c++ multithreading atomic volatile c++11

在回答这个问题时,关于OP的情况的另一个问题出现了,我不确定:它主要是处理器架构问题,但也有关于C++ 11内存模型的连锁问题.

基本上,由于以下代码(为简单起见略微修改),OP的代码在更高的优化级别上无限循环:

while (true) {
    uint8_t ov = bits_; // bits_ is some "uint8_t" non-local variable
    if (ov & MASK) {
        continue;
    }
    if (ov == __sync_val_compare_and_swap(&bits_, ov, ov | MASK)) {
        break;
    }
}
Run Code Online (Sandbox Code Playgroud)

__sync_val_compare_and_swap()GCC的原子CAS 在哪里内置.GCC(合理地)在进入循环之前bits_ & mask检测到的情况下将其优化为无限循环,true完全跳过CAS操作,因此我建议进行以下更改(可行):

while (true) {
    uint8_t ov = bits_; // bits_ is some "uint8_t" non-local variable
    if (ov & MASK) {
        __sync_synchronize();
        continue;
    }
    if (ov == __sync_val_compare_and_swap(&bits_, ov, ov | MASK)) {
        break;
    }
}
Run Code Online (Sandbox Code Playgroud)

我回答后,OP指出,改变bits_volatile uint8_t似乎正常工作.我建议不要走这条路,因为volatile通常不应该用于同步,并且在这里使用栅栏似乎没有多少缺点.

但是,我想的更多,在这种情况下语义是这样的,如果ov & MASK检查是基于陈旧的值,只要它不是基于无限期陈旧的(即只要循环最终被破坏),因为实际的更新尝试bits_是同步的.所以这volatile足以保证这个循环最终终止,如果bits_由另一个线程更新bits_ & MASK == false,对于任何现有的处理器?换句话说,在没有明确的内存栅栏的情况下,编译器未优化的读取实际上是否可能由处理器有效地优化,而是无限期地?(编辑:要清楚,我在这里问一下现代硬件可能实际做了什么,因为假设读取是由编译器在循环中发出的,所以从技术上讲它不是一个语言问题,尽管用C++语义来表达它是方便的.)

这是它的硬件角度,但是稍微更新它并使它也成为关于C++ 11内存模型的一个可回答的问题,请考虑以下代码的变化:

// bits_ is "std::atomic<unsigned char>"
unsigned char ov = bits_.load(std::memory_order_relaxed);
while (true) {
    if (ov & MASK) {
        ov = bits_.load(std::memory_order_relaxed);
        continue;
    }
    // compare_exchange_weak also updates ov if the exchange fails
    if (bits_.compare_exchange_weak(ov, ov | MASK, std::memory_order_acq_rel)) {
        break;
    }
}
Run Code Online (Sandbox Code Playgroud)

cppreference声称std::memory_order_relaxed暗示"对原子变量周围的内存访问的重新排序没有限制",因此独立于实际硬件将要或不会做什么,这意味着在符合实现的另一个线程上更新之后bits_.load(std::memory_order_relaxed)技术上永远不会读取更新的值bits_

编辑:我在标准中找到了这个(29.4 p13):

实现应该使原子存储在合理的时间内对原子载荷可见.

所以显然等待"无限长"的更新价值(大多数情况下?)是不可能的,但除了应该是"合理的"之外,没有任何特定的新鲜时间间隔的硬性保证; 然而,关于实际硬件行为的问题仍然存在.

Pet*_*ker 9

C++ 11 atomics处理三个问题:

  1. 确保在没有线程切换的情况下读取或写入完整的值; 这可以防止撕裂.

  2. 确保编译器不会在原子读或写的线程内重新排序指令; 这确保了线程内的排序.

  3. 确保(适当选择内存顺序参数)在原子写入之前写入线程内的数据将被读取原子变量并查看写入值的线程看到.这是可见性.

当您使用时,memory_order_relaxed您无法保证放松的商店或负载的可见性.你得到前两个保证.

实现"应该"(即鼓励)使记忆写入在合理的时间内可见,即使是放松的排序.那可以说是最好的; 迟早这些事情应该出现.

所以,是的,正式地说,一个从未使宽松读取可见的轻松写入的实现符合语言定义.在实践中,这不会发生.

至于什么volatile,请询问您的编译器供应商.这取决于实施.