参考一

原子的概念

原子操作就是这样的一种『小到不可分割的』操作.

对于int sum;sum+=i这样的操作,汇编也是好几条指令,多个线程都访问时,也可能不是原子的。

atomic

  • 原子数据类型

atomic头文件。可以像使用内置的数据类型那样使用原子数据类型(c++保证这些操作是原子操作)。对应于内置的数据类型,原子数据类型都有一份对应的类型。

2018-02-03-11-19-50

原子本质就是锁,不过颗粒度更小。实现原子可以使用 cpu 的专用原子指令,或者自己实现一个锁。

<atomic>特定化的类型都有一个is_lock_free函数,可以查询某原子类型的操作是直接用的原子汇编指令(x.is_lock_free()返回 true),还是编译器和库内部用了一个锁(x.is_lock_free()返回 false)。

std::atomic_int sum  += i;

原子类型是不能赋值和拷贝的构造的,只能用load,store这样的操作,当然还有更多更复杂的操作,如:

  • exchange()

读-写-改,将新值改为 val,旧值返回。

T exchange (T val, memory_order sync = memory_order_seq_cst) volatile noexcept;
  • compare_exchage_strong()

比较/交换,比较当前值与 expected,若为 true,存储 val,false 存储 expected。

bool compare_exchange_strong (T& expected, T val,
           memory_order success, memory_order failure) noexcept;
  • compare_exchange_weak()

有可能 expected 等于当前值,但是返回 false.为某些 loop 而优化。

对于指针型的std::atomic<T*>,还有基本操作如

  • fetch_add()
  • fetch_sub()

存储地址上做原子加法和减法,为+=, -=, ++和–提供简易的封装;

class Foo{};
Foo some_array[5];
std::atomic<Foo*> p(some_array);
Foo* x=p.fetch_add(2);  // p加2,并返回原始值
  • atomic_flag

另外还有一类atomic_flag,非常适合做自旋锁,就是个布尔标志,没有is_lock_free函数,与锁无关,即线程访问时不需要加锁。

  • test_and_set() 如果 false 则 true,但是返回 flase ,如果 true 则 true
  • clear()
class spinlock_mutex
{
  std::atomic_flag flag;
public:
	//atomic_flag的初始操作是必须的std::atomic_flag f = ATOMIC_FLAG_INIT;
  spinlock_mutex():
    flag(ATOMIC_FLAG_INIT)
  {}
  void lock()
  {
	  //test_and_set并不会阻塞
	  //自旋锁,意味着锁是很快可以得到的,才会在短时间快速尝试。
	  //如果普通情况这样干巴巴的等,这个while耗不起。
    while(flag.test_and_set(std::memory_order_acquire));
  }
  void unlock()
  {
    flag.clear(std::memory_order_release);
  }
};

下面的使用方式,有么有觉得眼熟?防止互斥访问的,用的真的不少。

static bool flag = false;
if(flag== false) {flag = true; return false;} else {return true;}

内存顺序语义

顺序一致性是指线程执行的顺序和我们程序员所写代码的顺序是一致的。不然编译器判断执行顺序不影响程序结果的化,可能采用reorder的手段优化。

std::memory_order这个 enum 定义了 6 种语义, 来对内存操作的行为进行约定,这些语义分别规定了不同的内存操作在其它线程中的可见性问题, 默认的是顺序一致性。

typedef enum memory_order
    {
      memory_order_relaxed,   //不对执行顺序做任何保证,自由序列
      memory_order_consume,    //本线程中所有有关本原子类型的操作,必须等到本条原子操作完成之后进行,获取-释放序列
      memory_order_acquire,    //本线程中,后续的<strong>读操作</strong>必须在本条原子操作完成后进行,获取-释放序列
      memory_order_release,    // 本线程中,之前的<strong>写操作</strong>完成后才执行本条原子操作,获取-释放序列
      memory_order_acq_rel,    //memory_order_acquire和memory_order_release 效果的合并,获取-释放序列
      memory_order_seq_cst     //顺序一致,排序一致序列
} memory_order;

关于乱序及内存模型,一个不错的系列

知乎解答

cppreference

发现这部分异常艰深,先缓一缓。

atomic 使用限制总结

2018-02-04-18-28-09

非成员函数操作 atomic

不同的原子类型中也有等价的非成员函数存在,另外考虑了要与C语言兼容,在 C 中只能使用指针,而不能使用引用。后缀_exlicit可以指定内存顺序

  • std::atomic_load()
  • std::atomic_load_explicit()
  • std::atomic_store(&atomic_var,new_value)
  • std::atomic_is_lock_free()
  • std::atomic_flag_test_and_set()
  • std::atomic_flag_clear()

注意shared_ptr特殊,一般原子类型才能使用上述函数,但它类似原子类型

std::shared_ptr<my_data> p;
void process_global_data()
{
  std::shared_ptr<my_data> local=std::atomic_load(&p);
  process_data(local);
}