C++ 以宽松的顺序读取共享变量:理论上可能吗?在C++;?

C++ 以宽松的顺序读取共享变量:理论上可能吗?在C++;?,c++,multithreading,c++11,atomic,memory-model,C++,Multithreading,C++11,Atomic,Memory Model,考虑以下伪代码: expected = null; if (variable == expected) { atomic_compare_exchange_strong( &variable, expected, desired(), memory_order_acq_rel, memory_order_acq); } return variable; 当执行变量==预期的检查时,请注意没有“获取”语义 在我看来,所需的至少会被总共调用一次,每个线程最多调用一次

考虑以下伪代码:

expected = null;
if (variable == expected)
{
    atomic_compare_exchange_strong(
        &variable, expected, desired(), memory_order_acq_rel, memory_order_acq);
}
return variable;
当执行
变量==预期的检查时,请注意没有“获取”语义

在我看来,
所需的
至少会被总共调用一次,每个线程最多调用一次。
此外,如果
desired
从不返回
null
,则此代码将从不返回
null

现在,我有三个问题:

  • 上述情况是否一定属实?i、 例如,即使在每次读取都没有围栏的情况下,我们真的可以对共享变量进行有序读取吗

  • 在C++中实现这一点是可能的吗?如果是,怎么做?如果没有,原因是什么?
    (希望有一个理由,而不仅仅是“因为标准这么说”。)

    < >(2)如果答案是“是”,那么也可以在C++ +<强>中实现,而不需要< <强> >要求> >变量= =预期执行<代码>变量> < /> >

    的原子读取。 基本上,我的目标是了解在每个线程至少执行一次代码后,是否可以以与非共享变量的性能相同的方式执行共享变量的延迟初始化


    (这是一个“语言律师”问题。这意味着问题不是关于这是一个好的或有用的想法,而是关于在技术上是否可以正确地做这件事。)关于是否可以在C++中执行共享变量的惰性初始化的问题,

    < P>。(几乎)与非共享变量相同:

    答案是,这取决于硬件体系结构、编译器的实现和运行时环境。至少在某些环境中是可能的。特别是在使用GCC和Clang的x86上

    在x86上,可以在没有内存限制的情况下实现原子读取。基本上,原子读取与非原子读取是相同的。请查看以下编译单元:

    std::atomic<int> global_value;
    int load_global_value() { return global_value.load(std::memory_order_seq_cst); }
    
    我说的几乎一样,因为还有其他可能影响性能的原因。例如:

    • 虽然没有围栏,但原子操作仍然会阻止某些编译器优化,例如重新排序指令和消除存储和加载
    • 如果至少有一个线程在同一缓存线上写入不同的内存位置,那么它将对性能产生巨大影响(称为错误共享)
    话虽如此,实现延迟初始化的推荐方法是使用
    std::call_once
    。这将为所有编译器、环境和目标体系结构提供最佳结果

    std::once_flag _init;
    std::unique_ptr<gadget> _gadget;
    
    auto get_gadget() -> gadget&
    {
        std::call_once(_init, [this] { _gadget.reset(new gadget{...}); });
        return *_gadget;
    }
    
    std::once\u flag\u init;
    std::独特的ptr小工具;
    自动获取小工具()->gadget&
    {
    std::调用一次(_init,[this]{u gadget.reset(newgadget{…});});
    返回*小工具;
    }
    
    这是未定义的行为。您正在
    至少在某些线程中,这意味着所有访问
    变量必须受到保护。特别是在
    在一个线程中执行
    atomic\u compare\u exchange\u strong
    , 没有什么可以保证另一个线程可以看到
    变量的新值
    在它看到可能发生的写入之前 在
    desired()
    atomic\u compare\u exchange\u strong

    仅保证执行它的线程中的任何顺序。)NOSID:不,见第3条。问题是关于原子性是否需要,不管内存排序问题。@ NOSID:我没有提到它,因为我提到的第一件事就是伪代码,所以不要期望它是有效的C++。重点是概念;C++只是整个问题的一个方面。
    variable
    很大,因此
    atomic\u compare\u exchange\u strong
    必须使用互斥锁?当对象被更改时,您可以尝试访问
    variable==expected
    ,这意味着例如它的类不变量不必保持不变。如果没有描述多线程读取方式的内存模型,伪代码就没有什么意义/C++ C++ 11有C++的内存模型,但是C++的变量有类型,这些类型会影响C++在11内存模型下的行为。@ ykk:我不愿意使用C++内存模型,因为大多数问题都是概念性的,而不是C++特定的。只有部分问题问这是否可以在C++中实现,而对于SEC来说。当然,你可以给变量任何你认为合适的类型,使其发挥作用。你能想象在任何(理智的)记忆模型上有一个令人信服的理由(即,不仅仅是因为“记忆模型这么说”,而是为什么它会这么说)出现这种失败吗?我不确定我是否理解。您是否担心
    desired
    可能会执行的内存写入?如果
    desired
    没有执行任何内存写入,而只是返回一个本身很重要的值,您的答案会改变吗?(假设它返回的是一个整数而不是指针,或者说
    desired
    中有一个内存屏障,确保在返回之前所有内容都是可见的。)此外,问题的关键是,为什么每个访问都应该受到保护?不保护
    变量
    的读取不会因为以后的比较和交换而导致正确的代码吗?没有多少真正需要理解的。如果一个对象可以在多个线程中访问,并且您在任何线程中修改它,那么所有访问必须同步到它的es,否则您会有未定义的行为。关于在
    所需的
    中写入的问题只是一个可能出错的例子。我不是问什么可能出错(这是标准所说的),而是问它为什么会出错(标准可能会说的基本原理).所以我不明白会发生什么
    
    std::once_flag _init;
    std::unique_ptr<gadget> _gadget;
    
    auto get_gadget() -> gadget&
    {
        std::call_once(_init, [this] { _gadget.reset(new gadget{...}); });
        return *_gadget;
    }