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个条件:
- 连续的内存空间
- 拷贝构造函数需要拷贝全部的bits (memcpy)
- 没有虚函数和 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 会互相等待。
特别对于写的操作会互相等待
对于读的操作可以更好的效率
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 -
std::memory_order_acquire
Acquire barrier保证所有的在barrier之后的程序内存操作是可见的。
读或者写不会被reordered 到barrier的后面。 -
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是非常影响性能的。