C++ 在忙等待循环中是否需要内存屏障或原子操作?

C++ 在忙等待循环中是否需要内存屏障或原子操作?,c++,multithreading,gcc,memory-barriers,spinlock,C++,Multithreading,Gcc,Memory Barriers,Spinlock,考虑以下spin_lock()实现,最初来自: 我已经知道: volatile防止编译器在循环的每次迭代中优化出*锁重新读取 volatile 这种实现实际上适用于GCC forx86(例如在Linux内核中)和一些其他体系结构 通用体系结构的spin_lock()实现中至少有一个内存和编译器屏障;此示例将它们插入到中 问题: volatile是否足够,或者是否有任何体系结构或编译器在while循环中需要内存、编译器屏障或原子操作 1.1是否符合C++标准 1.2实际上,对于已知的体系结构和

考虑以下
spin_lock()
实现,最初来自:

我已经知道:

  • volatile
    防止编译器在
    循环的每次迭代中优化出
    *锁
    重新读取
    
    
  • volatile
  • 这种实现实际上适用于GCC for
    x86
    (例如在Linux内核中)和一些其他体系结构
  • 通用体系结构的
    spin_lock()
    实现中至少有一个内存和编译器屏障;此示例将它们插入到中
问题:

  • volatile
    是否足够,或者是否有任何体系结构或编译器在
    while
    循环中需要内存、编译器屏障或原子操作

    1.1是否符合
    C++
    标准

    1.2实际上,对于已知的体系结构和编译器,特别是对于GCC及其支持的平台

  • 这个实现在GCC和Linux支持的所有体系结构上都是安全的吗?(至少在某些体系结构上是低效的,对吗?)
  • 根据
    C++11
    及其内存模型,while
    循环是否安全

  • 有几个相关的问题,但我无法从中得出明确的答案:

    • 原则上:是的,如果程序执行从一个内核移动到下一个内核,它可能看不到前一个内核上发生的所有写操作

    • 在几乎所有现代体系结构上,缓存(如L1和L2缓存)都由硬件保证一致性。无需刷新任何缓存即可使内存对其他CPU可见

    来自:

    。。。其他体系结构(如安腾)提供了单独的“获取”和“释放”内存屏障,分别从读卡器(接收器)或写卡器(源)的角度解决了写后读操作的可见性问题


    对我来说,这意味着安腾需要一个合适的围栏,以使读/写对其他处理器可见,但实际上这可能只是为了订购。我认为,这个问题实际上可以归结为:

    是否存在这样一种架构:如果没有指示处理器更新其本地缓存,处理器可能永远不会更新其本地缓存?我不知道答案,但如果您以这种形式提出问题,那么其他人可能会问。在这样的体系结构中,您的代码可能会进入一个无限循环,在这个循环中,
    *lock
    的读取总是看到相同的值

    在一般C++合法性方面,在你的示例中设置一个原子测试是不够的,因为它只实现了一个单独的栅栏,当进入while循环时,它可以让你看到*Cux<代码>的初始状态,但不知道它何时改变。(这会导致未定义的行为,因为您正在读取一个变量,该变量在另一个线程中更改,而没有同步)-因此,您的问题(1.1/3)的答案是否定的

    另一方面,在实践中,(1.2/2)的答案是肯定的(给出),只要该体系结构能够保证缓存一致性,而没有显式的内存限制,这在x86中是正确的,可能对许多体系结构也是如此,但我不能给出一个明确的答案,说明它是否适用于GCC支持的所有体系结构。然而,通常情况下,故意依赖技术上未定义的代码的特定行为是不明智的根据语言规范定义行为,特别是如果不这样做就可以得到相同的结果

    顺便说一句,考虑到
    内存\u顺序\u松弛
    的存在,在这种情况下,似乎没有什么理由不使用它,而不是尝试使用非原子读取进行手动优化,即将示例中的while循环更改为:

        while (atomic_load_explicit(lock, memory_order_relaxed)) {
            cpu_relax();
        }
    

    在X8664中,例如原子负载成为常规的代码<代码> MOV 指令,优化的汇编输出与原来的例子基本相同。

    这是重要的:在C++ >代码> Value中,<强> >无> /强>与并发有关!<代码>易失性< /代码>的目的是告诉T编译器认为它不会优化对受影响对象的访问。它不会告诉CPU任何事情,主要是因为CPU已经知道内存是否是易失性的。

    易失性的目的是有效地处理内存映射的I/O

    C++的标准在第1.10节[内多线程]中非常清楚,对一个线程中修改的对象进行非同步访问,并被访问(修改或读取)。在另一个线程中,是未定义的行为。避免未定义行为的同步原语是库组件,如原子类或互斥体。本条仅在信号上下文(即as

    volatile sigamatic\u t
    )和向前进程上下文中提到
    volatile
    (也就是说,线程最终将执行一些具有可观察效果的操作,如访问
    volatile
    对象或执行i/O)。没有提到与同步相关的
    volatile

    因此,对线程间共享的变量进行不同步的评估会导致未定义的行为。是否声明为
    volatile
    与此未定义的行为无关

  • 这里的易失性是否足够,或者是否存在在while循环中需要内存、编译器屏障或原子操作的架构或编译器
  • 易失性代码是否会看到变化。是的,但不一定像内存障碍一样快。在某个时刻,会发生某种形式的同步,并且会从变量中读取新状态,但无法保证代码中其他地方发生了多少变化

    < C++ > 1.1
        while (atomic_load_explicit(lock, memory_order_relaxed)) {
            cpu_relax();
        }