比较并交换C ++ 0x

时间:2010-11-18 09:54:29

标签: c++ multithreading gcc concurrency compare-and-swap

来自C++0x proposal关于C ++原子类型和操作:

  

29.1顺序和一致性[atomics.order]

     

添加一个带有以下段落的新子句。

     

枚举memory_order指定详细的常规(非原子)内存同步顺序,如[由N2334或其采用的后继者添加的新部分]中定义的,并且可以提供操作排序。其列举的值及其含义如下。

     
      
  • memory_order_relaxed
  •   
     

操作不会命令记忆。

     
      
  • memory_order_release
  •   
     

对受影响的内存位置执行释放操作,从而使常规内存写入通过应用它的原子变量对其他线程可见。

     
      
  • memory_order_acquire
  •   
     

对受影响的内存位置执行获取操作,从而在通过应用它的原子变量释放的其他线程中进行常规内存写入,对当前线程可见。

     
      
  • memory_order_acq_rel
  •   
     

该操作具有获取和释放语义。

     
      
  • memory_order_seq_cst
  •   
     

该操作具有获取和释放语义,此外,还具有顺序一致的操作顺序。

提案中较低:

bool A::compare_swap( C& expected, C desired,
        memory_order success, memory_order failure ) volatile
     

其中可以指定CAS的内存顺序。


我的理解是“memory_order_acq_rel”只需要同步操作所需的那些内存位置,而其他内存位置可能保持不同步(它不会像内存栅栏一样)。

现在,我的问题是 - 如果我选择“memory_order_acq_rel”并将compare_swap应用于整数类型(例如整数),这通常如何转换为现代消费者处理器上的机器代码,例如多核英特尔i7?那么其他常用的架构(x64,SPARC,ppc,arm)呢?

特别是(假设一个具体的编译器,比如gcc):

  1. 如何使用上述操作比较和交换整数位置?
  2. 这样的代码会产生什么指令序列?
  3. i7上的操作是否无锁?
  4. 这样的操作是否会运行完整的缓存一致性协议,同步不同处理器内核的缓存,就好像它是i7上的内存栅栏一样?或者它只是同步此操作所需的内存位置?
  5. 与之前的问题相关 - 在i7上使用acq_rel语义是否有任何性能优势?其他架构怎么样?
  6. 感谢所有答案。

2 个答案:

答案 0 :(得分:7)

这里的答案并非无足轻重。究竟发生了什么,意味着什么取决于许多事情。为了基本了解缓存一致性/内存,我最近的博客文章可能会有所帮助:

但除此之外,让我试着回答几个问题。首先,以下指令对于支持的内容非常有希望。

compare_swap( C& expected, C desired,
        memory_order success, memory_order failure )

架构并不都能完全按照您的要求实现。指定memory_order时,您指定了重新排序的工作方式。要使用英特尔的术语,您将指定您想要的围栏类型,其中有三个围栏,完整围栏,载荷围栏和商店围栏。仅仅因为你想要在该操作上使用特定的栅栏并不意味着它得到支持,我希望它总是会回到完全的栅栏。

编译器可能会使用CMPXCHG instructuion来实现调用。如果你指定了一些非宽松的东西,它将用lock标记,以表明该函数应该是原子的。这是否“无锁”取决于你在“锁定”方面的想法。

在内存同步方面,您需要了解缓存一致性的工作原理(我的博客可能会有所帮助)。新CPU使用ccNUMA架构(以前称为SMP)。本质上,内存上的“视图”永远不会失去同步。代码中使用的栅栏实际上并不强制任何冲洗本身发生。如果两个核心都在高速缓存行中缓存了相同的内存位置,则一个将被标记为脏,另一个将根据需要重新加载。 非常复杂的过程的简单解释

要回答您的上一个问题,您应该始终使用逻辑上需要正确的内存语义。大多数体系结构都不支持您在程序中使用的所有组合。但是,在许多情况下,您会得到很好的优化,特别是在您所请求的订单没有围栏的情况下(这很常见)。

- 一些评论的答案:

您必须区分执行写入指令和写入内存位置的含义。这是我试图在我的博客文章中解释的内容。当“0”提交到0x100时,所有核心都看到零。写入整数也是原子的,即使没有锁定,当您写入某个位置时,如果他们希望使用它,所有核心将立即具有该值。

问题在于,要使用您可能先将其加载到寄存器中的值,之后对该位置的任何更改显然都不会触及寄存器。这就是为什么人们需要互斥锁,尽管缓存一致的内存。

对于相互矛盾的说法,通常你会看到各种各样的说法。它们是否相互矛盾直接归结为“看到”“加载”“执行”在上下文中的含义。如果将“1”写入0x100,这是否意味着您执行了写入指令或CPU是否实际提交了该值。差异来自重新排序。 CPU可以延迟写入“1”,但是你可以确定它最终提交“1”所有内核看到它的那一刻。围栏控制着这种顺序。

答案 1 :(得分:1)

您的整个世界观似乎不合时宜:您的问题暗示缓存的一致性由C ++级别的内存顺序和CPU级别的篱笆或原子操作控制。

但是,高速缓存一致性是物理体系结构最重要的不变式之一,它由存储系统始终提供,该存储系统由所有CPU和RAM的互连组成。您永远无法从运行在CPU上的代码中击败它,甚至无法看到其操作细节。当然,通过直接观察RAM并在其他地方运行代码,您可能会在某些内存级别上看到过时的数据:根据定义,RAM并不是所有内存位置的最新值。

但是在CPU上运行的代码无法直接访问DRAM,只能通过内存层次结构来访问DRAM,这些内存层次结构相互通信以保持此共享内存视图的一致性。 (Typically with MESI)。即使在单核上,回写式高速缓存也可使DRAM的值过时,这对于非高速缓存一致性DMA可能是一个问题,但对于从CPU读取/写入内存则不是一个问题。

因此,此问题仅存在于外部设备,并且仅存在执行非一致性DMA的设备。 (DMA在现代x86 CPU上是缓存一致的; CPU内置的内存控制器可以实现这一点。)

  

这样的操作将运行完整的缓存一致性协议,   同步不同处理器核心的缓存,就好像它是一个   i7上的内存栅栏?

它们已经同步。请参见Does a memory barrier ensure that the cache coherence has been completed?-内存屏障仅在运行屏障的内核内部执行本地操作,例如刷新存储缓冲区。

  

还是只是同步存储位置   该操作需要吗?

原子操作仅适用于一个内存位置。您还要考虑其他哪些位置?

在弱排序的CPU上,memory_order_relaxed原子增量可以避免在该增量之前看到较早的加载/存储。但是x86的强排序内存模型不允许这样做。