https://baptiste-wicht.com/categories/c%2B%2B11-concurrency-tutorial.html

https://baptiste-wicht.com/posts/2012/07/c11-concurrency-tutorial-part-4-atomic-type.html

上面可以先看一下;

概述

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

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

想想典型的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:不对执行顺序做保证,编译器和处理器可以随意优化

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

.
.

总结

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

其实看到 这里,学过 JAVA的同学;语言同源,基本是一样的;

c++11多线程编程 整理(五) 原子量 atomic相关推荐

  1. Linux与C++11多线程编程(学习笔记)

    多线程编程与资源同步 在Windows下,主线程退出后,子线程也会被关闭; 在Linux下,主线程退出后,系统不会关闭子线程,这样就产生了僵尸进程 3.2.1创建线程 Linux 线程的创建 #inc ...

  2. 5天玩转C#并行和多线程编程 —— 第五天 多线程编程大总结

    5天玩转C#并行和多线程编程 -- 第五天 多线程编程大总结 5天玩转C#并行和多线程编程系列文章目录 5天玩转C#并行和多线程编程 -- 第一天 认识Parallel 5天玩转C#并行和多线程编程 ...

  3. C++11多线程---future、shared_future、atomic

    目录 一.std::future的其他成员函数 二.std::shared_future 三.原子操作std::atmic 在上篇:C++11多线程---async.future.package_ta ...

  4. c++11多线程编程同步——使用条件变量condition variable

    简述 在多线程编程中,当多个线程之间需要进行某些同步机制时,如某个线程的执行需要另一个线程完成后才能进行,可以使用条件变量. c++11提供的 condition_variable 类是一个同步原语, ...

  5. c++11 多线程编程(五)------unique_lock

    互斥锁保证了线程间的同步,但是却将并行操作变成了串行操作,这对性能有很大的影响,所以我们要尽可能的减小锁定的区域,也就是使用细粒度锁. 这一点lock_guard做的不好,不够灵活,lock_guar ...

  6. c++11 多线程编程(一)------初始

    什么是并发 并发在生活中随处可见,边走路边说话,边听歌边写代码.计算机术语中的"并发",指的是在单个系统里同时执行多个独立的活动,而不是顺序的一个接一个的执行.对于单核CPU来说, ...

  7. c++11 多线程编程(六)------条件变量(Condition Variable)

    互斥锁std::mutex是一种最常见的线程间同步的手段,但是在有些情况下不太高效. 假设想实现一个简单的消费者生产者模型,一个线程往队列中放入数据,一个线程往队列中取数据,取数据前需要判断一下队列中 ...

  8. c++11 多线程编程(三)------ 竞争和互斥锁

    竞争条件 并发代码中最常见的错误之一就是竞争条件(race condition).而其中最常见的就是数据竞争(data race),从整体上来看,所有线程之间共享数据的问题,都是修改数据导致的,如果所 ...

  9. c++11 多线程编程(二)------ 线程类构造函数深入理解

    构造函数的参数 std::thread类的构造函数是使用可变参数模板实现的,也就是说,可以传递任意个参数,第一个参数是线程的入口函数,而后面的若干个参数是该函数的参数. 第一参数的类型并不是c语言中的 ...

  10. c++11多线程编程(一):创建线程的三种方法

    c++11线程库 原始的c++标准仅支持单线程编程,新的c++标准(c++11或c++0x)于2011年发布,引入了新的线程库. 编译器要求 Linux: gcc 4.8.1 (完全并发支持) Win ...

最新文章

  1. 一首凉凉送给这些学校硕博生!本学期不必返校!这些高校已经发布通知了
  2. 俞敏洪+摆脱恐惧+世界想让你做一个平凡的人、你信了吗
  3. 协方差中的正相关与负相关 指的是线性代数中的线性相关
  4. 有什么类型的MPLS?
  5. Extended Twin Composite Number
  6. Python模拟登录实战,采集整站表格数据
  7. Java8 stream用法-备忘录
  8. IBM Machine Learning学习笔记(一)——Exploratory Data Analysis for Machine Learning
  9. Kali WiFi嗅探破解与字典生成
  10. linux rm rf 恢复删除文件,rm -rf 删除文件找回
  11. dos 教程(很全的)
  12. itext设置字体间距_微信公众号文章字体怎么修改?行间距、字间距一般设置多少?...
  13. 微信改微信号连接服务器,微信修改不了微信号怎么回事 微信号怎么修改
  14. 【AI视野·今日NLP 自然语言处理论文速览 第十三期】Wed, 23 Jun 2021
  15. CPU,GPU,NPU的架构差异对比
  16. hadoop2.7.2下载
  17. 53 张图详解防火墙的 55 个知识点
  18. 【大学生Python】字典的基础使用
  19. 全国计算机等级报名登记表,全国计算机等级考试报名报名流程
  20. linux程序启动后查不到进程,Linux应用程序 启动流程

热门文章

  1. 翻译软件Bob安装教程
  2. 【Win10】【开始菜单打不开】任务栏修复
  3. base64、File、Blob、ArrayBuffer互转
  4. 基于区块链技术的展望:搭建商业银行供应链管理和供应链金融服务平台
  5. 自动升级系统的设计与实现(源码)
  6. Android:LayoutInflater(布局服务)的 简单介绍 使用方法解析
  7. oracle sqldeveloper 115网盘 提取码
  8. 南京大学俞扬:环境模型学习——让强化学习走出游戏
  9. vbscript for 转 php for,VBS教程:VBscript语句-For...Next 语句
  10. topaz滤镜 V1.31中文版