c++ atomic 详细讲解

1. 什么样的type 可以转化成atomic?

任何 trivially copyable 的数据可以被转换成atomic.

#include <iostream>
#include <type_traits>
 
struct A {
    int m;
};
 
struct B {
    B(B const&) {}
};
 
struct C {
    virtual void foo();
};
 
struct D {
    int m;
 
    D(D const&) = default; // -> trivially copyable
    D(int x): m(x+1) {}
};
 
int main()
{
    std::cout << std::boolalpha;
    std::cout << std::is_trivially_copyable<A>::value << '\n';
    std::cout << std::is_trivially_copyable<B>::value << '\n';
    std::cout << std::is_trivially_copyable<C>::value << '\n';
    std::cout << std::is_trivially_copyable<D>::value << '\n';
}

Output:

true
false
false
true

trivally copyable 需要满足3个条件:

  1. 连续的内存空间
  2. 拷贝构造函数需要拷贝全部的bits (memcpy)
  3. 没有虚函数和 noexcept constructor
struct S{
int q=1;
}
std::atomic<S> s;

2.atomic 支持哪些操作

std::atomic<int> x(0);

++x;  // Atomic pre-increment
x++;  // Atomic post-increment
x +=1;  // Atomic increment
x|=1;   // Atomic bit set
x *=2;  // 错误的写法,不支持乘法,编译器不通过
int y = x+1; // Atomic Read x
x = y+1;    // Atomic write x
x = x+1;   // Atomic Read followed by atomic write!
x = x*2;    // Atomic read followed by atomic write !

其中x = x+1; 和 x = x*2; 有两个原子的操作,在多线程里面会被解释成两条执行的指令。
例如:

#include <stdio.h>
#include <thread>
#define INC_TO 1000000 // one million...
#include <mutex>
#include <functional>
#include <atomic>
#include <vector>
int main()
{
  std::vector<std::thread> ts;

  std::atomic<int> a(0);

  auto  add_func = [&]() {
    a+=1;
  };

  int threads_num =20000;
  for(int i=0; i < threads_num; ++i) {
    ts.emplace_back(std::move(std::thread(add_func)));
  }

  for(int i=0; i < threads_num; ++i) {
    ts[i].join();
  }
  printf("final result:%d", a.load());
//  std::atomic<int> a(1); // Not std::atomic<int> a =1;
  return 0;
}

输出:
final result:19982

其他的操作:

std::atomic<T> x;

T y = x.load(); // 等价于 T y=x;
x.store(y);   //等价于 x =y;


// 2. atomic exchange
T z = x.exchange(y);   // 等于 T z = x;     x=y;


// 3. compare and swap(condition exchange)
T y ;
 bool succ = x.compare_exchange_strong(y, z);  //. 如果 x ==y , 那么 x =z; 并且返回true,    如果 x!=y,  那 y=z; 返回false
 

compare and swap (CAS)有什么特殊的地方吗?
CAS 被用在lock-free的程序里面,一个例子:

 std::atomic<int> a(12);
  int x = a;
  while(! a.compare_exchange_strong(x, x+1)) {
    printf("x:%d", x);
  }

使用CAS可以写出lock-free的代码,具体参考:https://zhuanlan.zhihu.com/p/53012280

两种操作:compare_exchange_strong 和 compare_exchange_weak

x.compare_exchange_strong(old_x, new_x)
x.compare_exchange_weak(old_x, new_x)
compare_exchange_strong和compare_exchange_weak的区别是,如果一些事情导致compare_exchange_weak失败,即使对于x==old_x.

bool compare_exchange_strong(T& old_v, T new_v) {
  T tmp = value; // atomic value
  if (tmp != old_v) {
    old_v= tmp;
    return false;
  }
  Locl L;   // Gte exclusive access
  tmp = value;  // value could have change
  if (tmp != old_v) { old_v = tmp; return false;}
  
  value = new_v;
  return true;
}
bool compare_exchange_weak(T& old_v, T new_v) {
  T tmp = value; // atomic value
  if (tmp != old_v) {
    old_v= tmp;
    return false;
  }
  TimedLock L; //Gte exclusive access or fail;
  if (! L.locked()) return false;   // old_v is correct;
  tmp = value;  // value could have change
  if (tmp != old_v) { old_v = tmp; return false;}
  value = new_v;
  return true;
}

3. Atomic 的效率

atomic 操作会互相等待吗?
结论是atomic 会互相等待。
特别对于写的操作会互相等待
对于读的操作可以更好的效率
c++ atomic 详细讲解
c++ atomic 详细讲解
c++ atomic 详细讲解

4. Atomic queue、List

int q[N];
std::atomic<size_t> front;
void push(int x) {
  size_t my_slot = front.fetch_add(1);  // atomic
  q[my_slot] = x;   // exclusive slot
}

atomic 可以作为非atomic memory 的index。

atomic list

struct Node {
  int x;
  Node* next;
};

std::atomic<Node*> head;
void push_front(int x) {
  Node* new_node = new Node;
  new_node->x =x;
  Node* old_node = head;
  while (head.compare_exchange_strong(old_node, new_node)) {
    new_node->next = old_node;
  }
}

atomic 变量是一个指针指向非atomic的内存。

5. Memory barriers

atomics 另一个比较重要的概念是Memory barriers.
Memory barriers也就是一个cpu核心造成的内存的改变变成其他核心cpu可见。

  • 如果不指定内存的顺序,异步读取数据几乎是不可能的
  • Memory barriers是全局的控制,对于所有的cpu核心生效
  • Memory barriers是通过硬件实现的
  • barriers一般是在读或者写的时候指定

c++11 支持标准的Memory barriers。

std::atomic<int> a(12);
a.load(std::memory_order_release)
  • std::memory_order_relaxed
    没有Memory barriers
    c++ atomic 详细讲解

  • std::memory_order_acquire
    Acquire barrier保证所有的在barrier之后的程序内存操作是可见的。
    读或者写不会被reordered 到barrier的后面。
    c++ atomic 详细讲解

  • std::memory_order_release
    release Barrier 保证所有的在barrier之前的内存操作,在barrier是可见的,之后的操作不回被reorder 到barrier之前。
    例如barrier之后的写内存的操作不会reordered到barrier之前。

  • std::memory_order_acq_rel
    acquire 和release barriers一般都是一起使用。
    线程1 写atomic variable x 通过acquire barrier。
    线程2 读atomic variable x 通过release barrier。
    发生在线程1在barrier之前的所有的内存写的操作(在代码order)是对于在barrier 之后的线程2是可以见的。
    线程1准备数据(一写操作)然后通过更新atomic variable x. releases(publishes) 写的结果.
    线程2 acquire atomic variable x 保证数据是可见的。

  • std::memory_order_acq_rel
    通过将acquire 和 release 绑定起来,保证所有的操作不能穿越barrier.
    仅仅当两个线程有相同的atomic variable.

  • std::memory_order_seq_cst
    最严格的内存序列, 原子变量单一总的修改顺序。最好不要用memory_order_seq_cst 这个是严重影响程序的性能的。

原子操作默认的是最严格的memory_order_seq_cst。不能reorder操作的顺序。

Memory barriers是非常影响性能的。

上一篇:HTML/CSS基础教程 三


下一篇:ndk编译jsoncpp