比较并交换C++ 0x

axe*_*l22 16 c++ concurrency multithreading gcc compare-and-swap

从关于C++原子类型和操作的C++ 0x提议:

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
Run Code Online (Sandbox Code Playgroud)

可以指定CAS的内存顺序.


我的理解是" memory_order_acq_rel"只需要同步操作所需的那些内存位置,而其他内存位置可能保持不同步(它不会表现为内存栅栏).

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

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

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

感谢所有的答案.

edA*_*a-y 7

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

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

compare_swap( C& expected, C desired,
        memory_order success, memory_order failure )
Run Code Online (Sandbox Code Playgroud)

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

编译器可能会使用CMPXCHGinstructuion来实现调用.如果你指定了一些非宽松的东西,它会标记这个lock以表明函数应该是原子的.这是否"无锁"在很大程度上取决于你在"锁定"方面的想法.

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

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

- 一些评论的答案:

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

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

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