代码之家  ›  专栏  ›  技术社区  ›  Lingxi

放松的记忆顺序会导致无限循环吗?

  •  8
  • Lingxi  · 技术社区  · 7 年前

    相关代码:

    #include <atomic>
    #include <thread>
    
    std::atomic_bool stop(false);
    
    void wait_on_stop() {
      while (!stop.load(std::memory_order_relaxed));
    }
    
    int main() {
      std::thread t(wait_on_stop);
      stop.store(true, std::memory_order_relaxed);
      t.join();
    }
    

    自从 std::memory_order_relaxed 在这里使用,我假设编译器可以自由地重新排序 stop.store() 之后 t.join() . 因此, t.连接() 永远不会回来。这个推理正确吗?

    如果是,将更改 stop.store(true, std::memory_order_relaxed) stop.store(true) 解决问题?

    2 回复  |  直到 7 年前
        1
  •  3
  •   T.C. Yksisarvinen    7 年前

    [介绍进度]/18:

    一个实现应该确保最后一个值(在修改中 顺序)由原子或同步操作分配的 在有限时间内对所有其他线程可见。

    [原子顺序]/12:

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

    这是一项不具约束力的建议。如果您的实现遵循它们——正如高质量的实现应该遵循的那样——那么您就可以了。否则,你就完蛋了。在这两种情况下,不管使用的内存顺序如何。


    C++抽象机器没有“重新排序”的概念。在抽象语义中,主线程存储在原子中,然后被阻塞,因此如果实现使存储在有限的时间内对加载可见,那么另一个线程将在有限的时间内加载该存储值并终止。相反,如果实现出于任何原因不这样做,那么另一个线程将永远循环。使用的记忆顺序是不相关的。

    我从来没有发现关于“重新排序”的推理是有用的。它将低级实现细节与高级内存模型混合在一起,并使事情变得更混乱,而不是更少。

        2
  •  1
  •   Maxim Egorushkin    7 年前

    当前翻译单元中定义不可用的任何函数都被视为I/O函数。假定这样的调用会产生副作用,编译器无法将以下语句移动到调用之前或将前面的语句移动到调用之后。

    [intro.execution] :

    读取volatile glvalue([basic.lval])指定的对象、修改对象、调用库I/O函数或调用执行任何这些操作的函数都是副作用,这是执行环境状态的变化。表达式(或子表达式)的计算通常包括值计算(包括确定GLVALUE评估对象的标识和获取先前分配给prVALUE评估对象的值)和副作用的开始。当对库I/O函数的调用返回或评估通过volatile glvalue的访问时,即使调用(如I/O本身)或volatile访问所暗示的某些外部操作可能尚未完成,也会认为副作用已完成。

    与完整表达式相关联的每个值计算和副作用在与下一个要评估的完整表达式相关联的每个值计算和副作用之前排序。

    在这里 std::thread 建造师和 std::thread::join 这些函数(它们最终调用当前tu中不可用的特定于平台的线程函数)是否有副作用? stop.store 也会导致副作用(记忆存储是副作用)。因此 停止存储 不能在之前移动 STD::线程 建设者或过去 STD::线程:连接 电话。

    推荐文章