由于一种没来由的好奇,花了一下午理解了下c++11新增的原子量特性,感觉网上大多数文章都没讲清楚。写下我的一些理解
首先来看什么是原子操作以及为什么引进它

概述

在多线程模式下为了保证线程安全,我们通常做法是给共享资源加互斥锁,在一段时间只能有一个线程访问并操作共享资源,其他线程都会阻塞,这样就在一些对性能要求很高的情况下可能加锁就显得有点过重,于是就想出能不能在汇编层面让系统来能保证线程不乱来,让一个线程能把它想做的事做完其他线程再来操作,这就引出原子操作的概念。

原子操作就是将一系列操作能看成一个原子,不可分割,要么每个步骤都完成,要么都不做,同时能够按照顺序进行

想想典型的i++问题,这其中包含了三个步骤,先是取i的值到寄存器中,然后是在寄存器中加1,然后又将值读回内存中。未加任何限制的多线程程序中这三个步骤就会交叉执行,可能存在线程A在寄存器加1,但还未将加1后的值又读回内存,线程B来读取此时内存的i值,也加1,这样最后的结果就是错误的。对于这种情况我们就可以想到可以把这三个步骤包装在一起组成一个原子操作。c++11引入atomic标准文件,用atomic<T>来定义原子量,就封装了一系列的原子操作,具体的见后文。

再来看另一种情况,在多线程中我们常常会使用条件变量

//线程A
int a = 1;
if(b == 0){do something...b = 1;
}
//线程B
if(b == 1){if(a == 1){ok}
}

你觉得最后能ok吗,其实是不一定的。由于编译器不同等级的优化和cpu的乱序执行,一个线程内的指令执行顺序可能会发生变化,即指令重排,在线程A中变量a和变量b是不相关的,编译器就不会注重她两谁先谁后,可能就会先进行if语句,再执行a的定义语句,但可以看到这样就会影响线程B执行的结果。试想这种情况可不可以明确告诉编译器对变量的读写操作顺序不能乱来,这个就可以通过内存序来实现,c++11引入了6种内存序,来作为原子量成员函数的参数,具体见后文

.
.

atomic

说了这些铺垫,下面来看看这个原子量具体是个什么东东。来看下它的结构体定义

template < class T > struct atomic {//判断atomic<T>中的T对象是否为lock_freebool is_lock_free() const volatile;bool is_lock_free() const;//基本的构造函数,重载函数atomic() = default;constexpr atomic(T val);atomic(const atomic &) = delete;atomic & operator=(const atomic &) = delete;atomic & operator=(const atomic &) volatile = delete;T operator=(T val) volatile;T operator=(T val);operator  T() const volatile;operator  T() const;//一系列封装的原子操作T exchange(T val, memory_order = memory_order_seq_cst) volatile;T exchange(T val, memory_order = memory_order_seq_cst);void store(T val, memory_order = memory_order_seq_cst) volatile;void store(T val, memory_order = memory_order_seq_cst);T load(memory_order = memory_order_seq_cst) const volatile;T load(memory_order = memory_order_seq_cst) const;bool compare_exchange_weak(T& expected, T val, memory_order = memory_order_seq_cst) volatile;bool compare_exchange_weak(T &, T, memory_order = memory_order_seq_cst);bool compare_exchange_strong(T &, T, memory_order = memory_order_seq_cst) volatile;bool compare_exchange_strong(T &, T, memory_order = memory_order_seq_cst);
};

是不是感觉成员很多,不慌。除去基本的构造函数,运算符重载函数,我们主要来看下面一系列的函数

    //一系列封装的原子操作T exchange(T val, memory_order = memory_order_seq_cst) volatile;T exchange(T val, memory_order = memory_order_seq_cst);void store(T val, memory_order = memory_order_seq_cst) volatile;void store(T val, memory_order = memory_order_seq_cst);T load(memory_order = memory_order_seq_cst) const volatile;T load(memory_order = memory_order_seq_cst) const;bool compare_exchange_weak(T& expected, T val, memory_order = memory_order_seq_cst) volatile;bool compare_exchange_weak(T &, T, memory_order = memory_order_seq_cst);bool compare_exchange_strong(T &, T, memory_order = memory_order_seq_cst) volatile;bool compare_exchange_strong(T &, T, memory_order = memory_order_seq_cst);

这几个函数主要就是实现对T的一些操作的封装,比如读写操作,比较and更改操作,其实这还只是一部分,对于不同的class T类型还有特化版本,里面还有一些不同的成员函数,但就处于先理解的目的,我们来看看是怎么用的,比如对于T是整数或指针就有对i++操作符的重载,使其自增操作成为一个原子操作,接下来我们来用用它

#include <iostream>
#include <atomic>
#include <vector>
#include <functional>
#include <thread>
using namespace std;std::atomic<long> sum;
const int count = 100000;void add()
{//每个线程来循环对sum实行加1操作for (int j = 0; j < count; ++j)sum++;
}int main()
{std::vector<std::thread> threads;//开始计时clock_t start = clock();for (int j = 0; j < 5; ++j)threads.push_back(std::move(std::thread(add)));for (auto & t : threads)t.join();//结束计时clock_t finish = clock();cout<<"result:"<<sum<<endl;cout<<"time:"<<finish -start<<"ms"<<endl;return 0;
}

可以看到最后的结果正确,如果把sum换成普通的变量结果就不对


那又来试试加锁

for (int j = 0; j < count; ++j){m.lock();sum++;m.unlock();}


可以看到运行时间比用原子量还是多了一个量级。
这里再提一点,atomic当class T为某些类型时其内部原子操作封装也是会用锁的…,可以用其成员函数is_lock_free()来判断这个类型的原子量是不是无锁的
.
.

内存序

可以看到atomic成员函数里有这样一个参数memory_order

 bool compare_exchange_weak(T& expected, T val, memory_order = memory_order_seq_cst) volatile;

这个就是前文提到的内存序,这里就简单说说了

typedef enum memory_order {memory_order_relaxed,memory_order_consume,memory_order_acquire,memory_order_release,memory_order_acq_rel,memory_order_seq_cst
} memory_order;

有这六种内存序,他们都是用于控制变量在不同线程间的顺序可见性,说白了就是让编译器按着我说得来不要自个乱动指令顺序,避免指令重排导致错误。

memory_order_acquire:要求当前调用之后的读写操作不会重排到前面来

memory_order_release:要求这个调用之前的读写操作不能重排到后面去

memory_order_acquire,memory_order_release一般配合使用,即得到获取释放一致性,就拿前面的条件变量的例子来继续说,将其中的变量b换成原子量

atomic<int> b;void A()
{int a = 0;b.store(1,memory_order_release);
}void B()
{if(b.load(memory_order_acquire) == 1)cout<<"ok"<<endl;if(a == 0){}
}

A中release就保证int a = 0 不会调到b.store后面运行
B中acquire就保证if(a == 0 )不会调到b.load的前面

这是最常用的一种无锁搭配。

memory_order_acq_rel:读取-修改-写回
memory_order_seq_cst:默认的内存序
memory_order_consume:防止在其后对原子变量有依赖的操作被重排到前面去
memory_order_relaxed:不对执行顺序做保证,编译器和处理器可以随意优化

其他四个都不怎么用,而且很容易出错。

.
.

总结

到此感觉也把我知道的写得差不多了,当下最普遍的还是加锁来保证线程安全,但很多时候加锁会显得很繁重,原子量就是从更底层的层面来保证线程安全,所以效率更高

c++11原子量atomic相关推荐

  1. 利用C++11原子量atomic实现自旋锁详解

    一.自旋锁 自旋锁是一种基础的同步原语,用于保障对共享数据的互斥访问.与互斥锁的相比,在获取锁失败的时候不会使得线程阻塞而是一直自旋尝试获取锁.当线程等待自旋锁的时候,CPU不能做其他事情,而是一直处 ...

  2. volatile和原子量atomic如何对抗编译器优化?

    废话文学上一次这么流行的时候,还是在上一次流行废话文学的时候. 抖音上有个相声演员,每天的更新就是各种片汤话和废话,絮絮叨叨一大堆,一句有用的信息都没有.评论区都是调侃:"哎吓死我了,他差点 ...

  3. java 原子量Atomic举例(AtomicReference)

    Java并发库提供了很多原子类来支持并发访问的数据安全性,除了常用的 AtomicInteger.AtomicBoolean.AtomicLong 外还有 AtomicReference 用以支持对象 ...

  4. Multi-thread--C++11中atomic的使用

    原子库为细粒度的原子操作提供组件,允许无锁并发编程.涉及同一对象的每个原子操作,相对于任何其他原子操作是不可分的.原子对象不具有数据竞争(data race).原子类型对象的主要特点就是从不同线程访问 ...

  5. C++11之atomic原子操作

    atomic介绍 atomic对int.char.bool等数据结构进行了原子性封装,在多线程环境中,对std::atomic对象的访问不会造成竞争-冒险.利用std::atomic可实现数据结构的无 ...

  6. c++ 11 原子操作库 (std::atomic)(二)

    定义于头文件 <atomic> atomic 类模板及其针对布尔.整型和指针类型的特化 每个 std::atomic 模板的实例化和全特化定义一个原子类型.若一个线程写入原子对象,同时另一 ...

  7. std::atomic原子操作

    1.原子操作介绍 在多线程编程中,经常使用互斥锁锁住一段代码块,实现线程同步.原子操作可以看成是对变量的互斥锁.比如程序中一个线程读取一个变量,另一个线程修改该变量的值,那么采用原子操作可以不用添加互 ...

  8. 【C++】原子操作(atomic)与无锁编程学习记录

    lambda std::bind 智能指针使用 深度库基于数据结构与算法的优化 atomic 操作与多线程 数据安全 原子内存操作 设计模式:单例模式 C++11原子操作与无锁编程 https://w ...

  9. 2016/09/16面试资料汇总

    Rood 内连接 全连接 高并发 多线程 怎么识别不同用户的session spring和springMVC有什么区别 session和cookie的区别 ibatis的几个文件 springMVC有 ...

  10. 关于thread中mutex相关内容的理解

    在C++11中,引入了thread线程库,而为了应对线程的同步,又引入了互斥量mutex,而又因为mutex的各种问题,后续又引申出了使得mutex更安全的机制. 下面就以我的个人理解,介绍一下. 值 ...

最新文章

  1. 快速构建深度学习图像数据集,微软Bing和Google哪个更好用?
  2. SliverLight注册字典转换器方法
  3. jQuery中ajax加载文本
  4. fileinput 时间_JavaScript_Bootstrap Fileinput文件上传组件用法详解,最近时间空余,总结了一些关...
  5. putty上传文件到linux_基于windows安装部署putty小工具及相关功能介绍
  6. python装饰器参数讲解_python装饰器的详细解析
  7. matlab怎么提取特征,matlab – 了解提取特征的编码
  8. 2.用Python套用Excel模板,一键完成原亮样式
  9. c程序设计停车场收费管理系统_智能车牌识别停车收费管理系统
  10. jQuery 增加 删除 修改select option
  11. MySQL经典书籍推荐
  12. 高质量实时渲染课程笔记(一)——介绍和概览(高质量实时渲染的含义、学习前置、涉及的内容目录、发展史)
  13. 谷歌google chrome浏览器Chrome版本太旧无法更新chrome无法更新至最新版本怎么办
  14. 微信扫一扫打印照片的原理以及实现过程(持续更新中)
  15. python下拉菜单_python下拉菜单
  16. 区块链技术正大肆颠覆价值数十亿美元的金融科技产业
  17. 服务器进不去系统system,system是什么进程 system进程可以关闭吗
  18. 华为三层交换机路由配置案例_华为三层交换与路由配置
  19. MATLAB程序设计与应用 2.4 MATLAB常用内部函数
  20. npm ERR! This is probably not a problem with npm. There is likely additional log ging output above.

热门文章

  1. 计算机网络:网络安全(电子邮件安全)
  2. ——黑马程序员——OC中Foundation下NSFileManager的使用
  3. Bigemap GIS Office软件 报价单
  4. 《软件架构/架构师书库》读后感
  5. 十六进制转二进制(C代码)
  6. android 的hook技术,Android Native Hook技术(一)
  7. 计算机学院毕设中期检查报告,毕业设计中期检查报告
  8. 影响力最大化 IC模型+贪心算法
  9. Android 静默安装的几种方式
  10. 下载并安装 J2SDK以及运行第一个java程序