无锁数据结构设计 之 详解C++内存顺序(Memory Order)


内存顺序概述

内存顺序,这是一个很大很大很大的坑,在介绍atomic原子类型的时候,就已经提及过,但是由于本身概念理解起来非常困难,所以没有细讲。现在就让我们仔细看看这是什么一个神奇的东西吧。

先通过一系列简单的代码片段,看一看内存顺序是如何定义的:

可见,memory_order一般情况下是加在有内存操作的函数(如store、load等)后面,比如上面程序中的 std::memory_order_release ,特别的由于函数compare_exchange_weak在失败、成功之后存在两种不同的内存操作策略,因此它可以传入两个memory_order分别指示成功(success)和失败(failure)后不同的操作策略:

 

内存顺序原理

好了,废话不多说,为了让读者理解内存顺序,我们将分别解释内存顺序、操作可见性等概念

内存顺序,顾名思义,是由于内存操作重排带来的不确定性。

CPU中的缓存机制曾经大幅提高了内存访问速度,这个机制将内存中经常访问的区域拷贝到了缓存中以加快速度。这种策略使得内存的读写的目标不一定是内存中的值,而是有可能仅仅是该值在缓存中的一个副本。

在单核处理器下,并不会出现任何问题,毕竟所有线程的缓存是共用的,也就是说不存在缓存同步的问题。在多核的情况下,问题就会比较复杂了,每一个核都有自己独立的L1缓存,若两个核共享内存,就需要解决缓存同步的问题。内存重排就是处理器(编译器)设计者为了平衡缓存同步的时间开销,和程序不稳定性之后得到的一个较好的解决方式。

不仅仅缓存会导致内存操作的重排,编译器也可能为了优化速度重排操作,当然,这些重排也是建立在不影响单线程程序正确性的情况下。不过,编译器的优化非常好处理,在解决好处理器优化后,编译器优化自然而然可以解决,因此我们这里就不深入讨论。

【注:在 之前的文章 中,我曾介绍过内存顺序可以用多人写作的版本控制来理解,单独线程的本地修改对于自身来说是一定有序的,但是这些修改要传递到远程的代码库中,则是一个可能发生顺序交换的不确定事件。  】

以上面的程序为例子:函数 write_x_then_y 依次写了变量x和y,而函数 read_y_then_x 在确认变量y已经被写之后读入x。从常理上来说,此时 z++ 是一定会被执行的。但是从运行结果上来看,assert可能被触发!

假设编译器没有优化汇编层面x和y的写入次序。实际上,是由于缓存机制,导致不同变量在其他线程看来更新的顺序是不同的。这就是内存顺序所刻画的问题。其中,本程序中,x的赋值操作可能不会被其他线程所看到,这就是所谓的不可见

从可见性方面重新叙述内存顺序的问题——一个线程的内存操作对于其他线程来说是不可见的。一种可能的情况是:

  • 线程A:写x,写y
  • 线程B:发现x先于y被赋值
  • 线程C:发现y先于x被赋值

联想之前说的缓存机制,确实会是这样的。

所以内存顺序memory_order是什么呢,memory_order是编译器指定常规的非原子内存访问如何围绕原子操作排序。

初步理解内存顺序

下面是我对于内存顺序的理解,由于在x8-64的机器上,内存顺序的问题本身不容易触发,所以下面的所有解释都没有经过验证,但是是我通过阅读网络上各种“不靠谱”的文献之后,经过自己筛选总结出的一套可信的解释。

memory_order_acquire & memory_order_release

在各种资料中,这两个内存顺序标记都是组合使用的,一个比较直观的理解是:线程A的release标记写操作W_A和线程B的acquire标记读操作R_B组合,可以达到:

  1. 线程A中的所有W_A之前的写操作,相对于W_A对齐,也就是说W_A操作完成后,线程A所有写操作完成
  2. 线程B中的所有R_B之后的读操作,相对于R_B对齐,也就是说R_A操作开始时,线程B的所有读操作尚未开始
  3. 线程A的W_A在线程B的R_B之前读入

综合上面三条性质,我们发现,acquire-release操作成功将线程A和线程B分割开来。

memory_order_relaxed

不进行内存顺序限制,即对于某一条语句,倘若运用了memory_order_relaxed标记,则其储存顺序对于其他线程不可见。那么什么时候使用这个标记呢?

既然acquire-release通过标记线程A的最后一个写操作,和线程B的第一个读操作,实现了线程的顺序要求,那么除了这两个操作之外的其他操作,实际上是可以直接用memory_order_relaxed的

原创文章地址:【无锁数据结构设计 之 详解C++内存顺序(Memory Order)】,转载时请注明出处mhy12345.xyz

《无锁数据结构设计 之 详解C++内存顺序(Memory Order)》有一个想法

发表评论

电子邮件地址不会被公开。 必填项已用*标注

This site uses Akismet to reduce spam. Learn how your comment data is processed.