如图所示,使用这些具有宽松内存顺序和 rel/acq 的原子操作,此 C++ 代码段是否有效?

Using these atomic operations with memory order relaxed and rel/acq as shown, does this C++ snippet work?

写在cppmem伪代码中:

int main()                                                                                                                                                                                   
{                                                                                                                                                                                            
  atomic_int n = -1;                                                                                                                                                                         
  atomic_int n2 = 0;                                                                                                                                                                         

  {{{                                                                                                                                                                                        
      {                                                                                                                                                                                      
        n2.store(1, mo_relaxed);                                                                                                                                                             
        if (n.load(mo_relaxed) != -1)                                                                                                                                                        
          n.store(1, mo_release);                                                                                                                                                            
      }                                                                                                                                                                                      
  |||                                                                                                                                                                                        
      {                                                                                                                                                                                      
        n.store(0, mo_release);                                                                                                                                                              
        int expected = 0;                                                                                                                                                                    
        do                                                                                                                                                                                   
        {                                                                                                                                                                                    
          desired = n2.load(mo_relaxed);                                                                                                                                                     
        }                                                                                                                                                                                    
        while (!n.compare_exchange_strong(&expected, desired, mo_acquire));                                                                                                                  
      }                                                                                                                                                                                      
  }}}                                                                                                                                                                                        

  assert(n == 1);                                                                                                                                                                            
}                                                                                                                                                                                            

换句话说,两个原子变量被初始化为n = -1和n2 = 0;

线程 1 首先将 1 写入 n2,然后写入 n,前提是 n 不是(仍然)-1。

线程 2 首先将 0 写入 n,然后加载 n2 并分配 n = n2,只要自上次读取 n 以来(或当 n 仍为 0 时)n 未更改。

两个线程加入后,n 必须在每次可能的执行中都等于 1。

此代码是我的一个开源项目的一部分,涉及将 streambuf 实现重置为无锁缓冲区的开头,同时两个线程同时读取和写入它。此特定部分与 'sync-ing'(或刷新写入输出)有关。

我设计了这个,它在每个操作顺序一致时工作(这是蛮力测试),但我无法理解内存顺序要求:/。

如果按以下顺序执行指令(和缓存更新),则可能会触发此断言:

  • 第一个线程 运行 它的所有指令。所以它只是将 n2 的值从 0 更改为 1.
  • 然后线程 2 运行s。首先它将 n 的值从 -1 更改为 0.
  • 然后线程 2 加载 n2(在 n2.load(mo_relaxed) 中)。此时没有同步,因此可以加载之前存储在 n2 中的任何值(包括初始化值,请参阅 [intro.race]/1)。假设它加载 0.
  • 所以threads 2的变量值分别是n==0n修改顺序的最后一个),n2==0expected==0desired==0之前比较交换指令。然后比较交换成功,将0存入n.

在两个线程的执行结束时,你得到 n==0n2==1

由于顺序一致性,我所描述的情况不会发生,因为如果线程 1 看到 n2==1 && n==-1,线程 2 就看不到 n2==0 && n==0

对于这个算法,我确信除了顺序一致性之外不可能使用任何其他内存顺序。

使用我在 https://plv.mpi-sws.org/rcmc/ 上找到的工具 我能够通过实验发现最宽松的要求是:

线程 1:

n2.store(1, std::memory_order_seq_cst);
if (n.load(std::memory_order_seq_cst) != -1)
  n.store(1, std::memory_order_release);

线程 2:

n.store(0, std::memory_order_seq_cst);
int expected = 0;
int desired;
do
{
    desired = n2.load(std::memory_order_seq_cst);
}
while (!n.compare_exchange_strong(expected, desired,
    std::memory_order_acquire, std::memory_order_relaxed));

编辑:

同一作者的更新工具(当然也更好) 现在可以从 https://github.com/MPI-SWS/genmc

下载

事实证明,该工具非常快速且有用,即使对于测试使用弱序原子的真实算法也是如此,例如我在这里所做的:genmc_buffer_reset_test.c

给定行的#include 是生成的 C 文件,从我的 C++ 代码中提取并使用 awk 脚本转换为 C,因为在我看来 genmc 只能(不幸地)用于 C 代码(?) .