C++

C++11-原子操作

Posted by w@hidva.com on March 11, 2016

基本概念

memory location

  • memory location;或者是 scalar type 类型对象,或者是具有非0宽的位域最长序列.
  • 多个线程同时读写多个 memory location 是线程安全的;多个线程以只读的方式访问同一 memory location 也是线程安全.仅当多个线程以同时以读写的方式访问同一 memory location(并且没有做任何形式的同步)时 该操作是线程不安全的.

object

  • object;就是一段内存区域.当然也具有其他属性,比如:type,object 的类型,决定了 object 的解析 方式;name,object 的名字等等.
  • subobject;当一个 object A 位于其他 object B 之内时,则 A 是 B 的 subobject;subobject 可以是:
    • base class subobject;即派生类对象中包含着基类子对象.
    • data member subobject;类对象中,其数据成员是该对象的子对象.
    • array element;array 类型对象本身是一个对象,所以数组内元素也是数组的子对象.

原子操作简介

  • 原子操作;操作是原子的,不会被其他线程看到操作的中间状态.
  • 原子类型;很显然原子类型对象的操作都是原子操作.

原子操作无锁?

  • 原子类型的操作是否是无锁的;通过原子类型is_lock_free()函数可以判断该原子类型的操作是否是无 锁的;(BTW,lock free 是无锁的意思,我当时还以为免费锁什么鬼的…)
  • std::atomic_flag;该类型对象上的所有操作都是无锁的.

std::memory_order 参数

  • std::memory_order参数;所有原子操作都带有这个参数,这篇文章大部分篇幅就是介绍 memory_order 参数.
  • 原子操作可以分为三种类型;如下:
    • 存储(store)操作;其std::memory_order参数可以取: std::memory_order_relaxed,std::memory_order_release,std::memory_order_seq_cst.
    • 载入(load)操作;其std::memory_order参数可以取: std::memory_order_relaxed,std::memory_order_seq_cst,std::memory_order_acquire,std::memory_order_consume.
    • 读-修改-写(read-modify-write)操作;其std::memory_order参数可以取: std::memory_order_relaxed,std::memory_order_seq_cst,std::memory_order_acquire,std::memory_order_consume, std::memory_order_release,std::memory_acq_rel.
  • 为了理解std::memory_order参数,必须理解内存模型.

理解内存模型

  • 内存模型一般分为静态内存模型,动态内存模型;静态内存模型用来指导编译器如何对对象之类的进行内存布局;
  • 动态内存模型;又称存取一致性模型;从行为上来看,是多个线程同时对同一对象进行读写操作时所做的约束,即 动态内存模型是一种约束,通过这种约束来保证当多个线程同时读写同一对象时的存取一致.
    • 现代编译器会进行指令重排,CPU 也会乱序执行;但这一切都是在内存模型的约束之下进行;身为程序员 的我们应该根据需要选择可以满足要求,具有最小约束的内存模型;这样在保证正确性的前提下性能最高.
  • std::memory_order参数共有6种可能,又可以划分为以下内存模型,每一种内存模型都具有不同的约束, 对程序员的要求,以及对性能的影响也各不同;
    • relaxed order
    • release-acquire order
    • release-consume order
    • sequentially-consistent order

非顺序一致性内存模型

  • 在以上std::memory_order取值中,除 sequentially-consistent order 之外,其他内存模型 均属于非顺序一致性内存模型;很显然 sequentially-consistent 属于顺序一致性内存模型.

  • 非顺序一致性内存模型强调的是同一份代码在不同的线程中可能会以不同的次序执行;因为 CPU 的 Cache, 内部缓存影响着指令的执行;但是共享对同一变量的连续修改顺序.即同一变量的连续修改顺序不会被编译器 重排,以及被 CPU 乱序.

memory_order

relaxed

  • 使用 relaxed 标记的操作并不是同步操作;即此时操作仅能保证原子性,以及修改顺序一致性(这个是非顺 序一致性内存模型的共性:同一变量的连续修改顺序不会改变.).
    • 按照我的理解就是:relaxed 标记的操作仅保证的操作的原子性,不是同步操作意思是:仅会通知当前 线程所在 CPU 让其缓存失效,而不会通知其他 CPU 缓存失效.也即其他线程可能看不到该操作的修改.
    // 一种可能的执行顺序是:DABC.
    // Thread 1:
    r1 = y.load(memory_order_relaxed); // A
    x.store(r1, memory_order_relaxed); // B
    // Thread 2:
    r2 = x.load(memory_order_relaxed); // C
    y.store(42, memory_order_relaxed); // D
    
  • relaxed 标记的操作一种使用场景用于自增引用计数.如在std::shared_ptr中.

release-acquire

  • If an atomic store in thread A is tagged memory_order_release and an atomic load in thread B from the same variable is tagged memory_order_acquire, all memory writes (non-atomic and relaxed atomic) that happened-before the atomic store from the point of view of thread A, become visible side-effects in thread B, that is, once the atomic load is completed, thread B is guaranteed to see everything thread A wrote to memory.

    • 使用互斥锁来比较,加锁操作相当于 acquire,释放锁操作相当于 release;线程 A,B 同时执行如 下代码,可知在 A 释放锁之后,B 拿到锁时,A 在临界区所做的所有改动对 B 都是可见的.

      lock();
      临界区;
      unlock();
      
  • 按照我的理解就是使用 release 标记的写操作具有原子性,以及自带内存屏障,保证在该写操作之前的写操 作不会被乱序到使用 release 标记的写操作之后.

  • acquire 的读操作,按照我的理解就是该操作会首先让当前 CPU 缓存失效,迫使 CPU 从内存中读取中最 新的值(这也就是在std::shared_ptr中为啥使用 relaxed-acquire.

release-consume

TODO …

Sequentially-consistent

TODO …

转载请注明出处!谢谢