c++ atomic 详细讲解
2021/5/22 22:56:09
本文主要是介绍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个条件:
- 连续的内存空间
- 拷贝构造函数需要拷贝全部的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是非常影响性能的。
这篇关于c++ atomic 详细讲解的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23增量更新怎么做?-icode9专业技术文章分享
- 2024-11-23压缩包加密方案有哪些?-icode9专业技术文章分享
- 2024-11-23用shell怎么写一个开机时自动同步远程仓库的代码?-icode9专业技术文章分享
- 2024-11-23webman可以同步自己的仓库吗?-icode9专业技术文章分享
- 2024-11-23在 Webman 中怎么判断是否有某命令进程正在运行?-icode9专业技术文章分享
- 2024-11-23如何重置new Swiper?-icode9专业技术文章分享
- 2024-11-23oss直传有什么好处?-icode9专业技术文章分享
- 2024-11-23如何将oss直传封装成一个组件在其他页面调用时都可以使用?-icode9专业技术文章分享
- 2024-11-23怎么使用laravel 11在代码里获取路由列表?-icode9专业技术文章分享
- 2024-11-22怎么实现ansible playbook 备份代码中命名包含时间戳功能?-icode9专业技术文章分享