原子操作的内存顺序

有六个内存顺序选项可应用于对原子类型的操作:

1. memory_order_relaxed

2. memory_order_consume

3. memory_order_acquire

4. memory_order_release

5. memory_order_acq_rel

6. memory_order_seq_cst。

除非你为特定的操作指定一个顺序选项,否则内存顺序选项对于所有原子类型默认都是memory_order_seq_cst。

6个内存顺序可以分为3类:

1. 自由顺序

(memory_order_relaxed)

2.获取-释放顺序

(memory_order_consume, memory_order_acquire, memory_order_release和memory_order_acq_rel)

3.排序一致顺序

(memory_order_seq_cst)

1、std::memory_order_relaxed “自由”内存顺序

在原子类型上的操作以自由序列执行,没有任何同步关系,仅对此操作要求原子性。例如,在某一线程中,先写入A,再写入B。但是在多核处理器中观测到的顺序可能是先写入B,再写入A。自由内存顺序对于不同变量可以自由重排序。

这是因为不同的CPU缓存和内部缓冲区,在同样的存储空间中可以存储不同的值。对于非一致排序操作,线程没有必要去保证一致性。

#include #include #include  std::atomic x,y;std::atomic z; void write_x_then_y(){  x.store(true,std::memory_order_relaxed);    y.store(true,std::memory_order_relaxed);  }void read_y_then_x(){  while(!y.load(std::memory_order_relaxed));    if(x.load(std::memory_order_relaxed))        z;}int main(){  x=false;  y=false;  z=0;  std::thread a(write_x_then_y);  std::thread b(read_y_then_x);  a.join();  b.join();  assert(z.load()!=0);  }

上述代码,z.load()!=0有可能会返回false。在b线程中,多核处理器观测到的顺序是随机的。b线程中的观测到的变量的并不会与线程a中的变量做同步,没有任何顺序要求。

2、std::memory_order_release “释放”内存顺序

使用memory_order_release的原子操作,当前线程的读写操作都不能重排到此操作之后。例如,某一线程先写入A,再写入B,再以memeory_order_release操作写入C,再写入D。在多核处理器中观测到的顺序AB只能在C之前,不能出现C写入之后,A或B再写入的情况。但是,可能出现D重排到C之前的情况。

memory_order_release用于发布数据,放在写操作的最后。

3、std::memory_order_acquire “获取”内存顺序

使用memory_order_acquire的原子操作,当前线程的读写操作都不能重排到此操作之前。例如,某一线程先读取A,再读取B,再以memeory_order_acquire操作读取C,再读取D。在多核处理器中观测到的顺序D只能在C之前,不能出现先读取D,最后读取C的情况。但是,可能出现A或B重排到C之后的情况。

memory_order_acquire用于获取数据,放在读操作的最开始 。

#include #include #include  std::atomic x,y;std::atomic z; void write_x_then_y(){  x.store(true,std::memory_order_relaxed);    y.store(true,std::memory_order_release);  }void read_y_then_x(){  while(!y.load(std::memory_order_acquire));  // 自旋,等待y被设置为true  if(x.load(std::memory_order_relaxed))        z;}int main(){  x=false;  y=false;  z=0;  std::thread a(write_x_then_y);  std::thread b(read_y_then_x);  a.join();  b.join();  assert(z.load()!=0);}

上述代码是使用“释放-获取"模型对“自由”模型的改进。z.load() != 0 返回的一定是true。首先,a线程中,y使用memory_order_release释放内存顺序,在多核处理器观测到的顺序,x的赋值肯定会位于y之前。b线程中,y的获取操作是同步操作,x的访问顺序必定在y之后,观测到的x的访问值一定为true。

“获取”与“释放”一般会成对出现,用来同步线程。

4、std::memory_order_acq_rel  "获取释放"内存顺序

memory_order_acq_rel带此内存顺序的读-改-写操作既是获得加载又是释放操作。没有操作能够从此操作之后被重排到此操作之前,也没有操作能够从此操作之前被重排到此操作之后。

std::atomic sync(0);void thread_1(){  // ...  sync.store(1,std::memory_order_release);} void thread_2(){  int expected=1;  while(!sync.compare_exchange_strong(expected,2,              std::memory_order_acq_rel))    expected=1;}void thread_3(){  while(sync.load(std::memory_order_acquire)<2);  // ...}

上述代码,使用memory_order_acq_rel来实现3个线程的同步。thread1执行写入功能,thread2执行读取功能。3个线程的执行顺序是确定的。compare_exchange_strong,当*this值与expected相同时,会将2赋值*this,返回true,不同时,将*this赋值expected,返回flase。

5、std::memory_order_consume 依赖于数据的内存顺序

memory_order_consume只会对其标识的对象保证该对象存储先行于那些需要加载该对象的操作。

struct X{int i;std::string s;}; std::atomic p;std::atomic a; void create_x(){  X* x=new X;  x->i=42;  x->s="hello";  a.store(99,std::memory_order_relaxed);    p.store(x,std::memory_order_release);  } void use_x(){  X* x;  while(!(x=p.load(std::memory_order_consume)))      std::this_thread::sleep(std::chrono::microseconds(1));  assert(x->i==42);    assert(x->s=="hello");    assert(a.load(std::memory_order_relaxed)==99);  /} int main(){  std::thread t1(create_x);  std::thread t2(use_x);  t1.join();  t2.join();}

x->i ==42,和x-> == "hello"会被确保已被赋值。但是a的值却是不确定的。加载p的操作标记为memory_order_consume,这就意味着存储p仅先行那些需要加载p的操作,对于a是没有保障的。

6、std::memory_order_seq_cst “顺序一致”内存顺序

memory_order_seq_cst比std::memory_order_acq_rel更为严格。memory_order_seq_cst不仅是一个"获取释放"内存顺序,它还会对所有拥有此标签的内存操作建立一个单独全序。memory_order_acq_rel的顺序保障,是要基于同一个原子变量的。memory_order_acq_rel使用了两个不同的原子变量x1, x2,那在x1之前的读写,重排到x2之后,是完全可能的,在x1之后的读写,重排到x2之前,也是被允许的。然而,如果两个原子变量x1,x2,是基于memory_order_seq_cst在操作,那么即使是x1之前的读写,也不能被重排到x2之后,x1之后的读写,也不能重排到x2之前,也就说,如果都用memory_order_seq_cst,那么程序代码顺序(Program Order)就将会是你在多个线程上都实际观察到的顺序(Observed Order)。

顺序一致是最简单、直观的序列,但是它也是最昂贵的内存序列,它需要对所有线程进行全局同步,比其他的顺序造成更多的消耗。因为保证一致顺序,需要添加额外的指令。

#include #include #include  std::atomic x,y;std::atomic z; void write_x(){  x.store(true,std::memory_order_seq_cst); } void write_y(){  y.store(true,std::memory_order_seq_cst);  }void read_x_then_y(){  while(!x.load(std::memory_order_seq_cst));  if(y.load(std::memory_order_seq_cst))        z;}void read_y_then_x(){  while(!y.load(std::memory_order_seq_cst));  if(x.load(std::memory_order_seq_cst))        z;}int main(){  x=false;  y=false;  z=0;  std::thread a(write_x);  std::thread b(write_y);  std::thread c(read_x_then_y);  std::thread d(read_y_then_x);  a.join();  b.join();  c.join();  d.join();  assert(z.load()!=0);  }

z.load() != 0 一定会为true。memory_order_seq_cst的语义会为所有操作都标记为memory_order_seq_cst建立一个单独全序。线程c和d总会有一个执行z ,x和y的赋值顺序,不管谁先谁后,在所有线程的眼中顺序都是确定的。

来源:盐焗咸鱼

https://blog.csdn.net/qq_33215865/article/details/88089927

C 的 6 种内存顺序,你都知道吗?相关推荐

  1. C 的 3种内存顺序,你都知道吗?

    1.std::memory_order_relaxed "自由"内存顺序 在原子类型上的操作以自由序列执行,没有任何同步关系,仅对此操作要求原子性.例如,在某一线程中,先写入A,再 ...

  2. 处理器协同机制其三C++内存顺序与栅栏(及依赖性读屏障)

    目录 处理器协同机制其一缓存一致性协议(MESI) 处理器协同机制其二内存屏障与内存顺序(及Store Buffer与Invalidate Queue) 处理器协同机制其三C++内存顺序与栅栏(及依赖 ...

  3. 处理器协同机制其二内存屏障与内存顺序(及Store Buffer与Invalidate Queue)

    目录 处理器协同机制其一缓存一致性协议(MESI) 处理器协同机制其二内存屏障与内存顺序(及Store Buffer与Invalidate Queue) 处理器协同机制其三C++内存顺序与栅栏(及依赖 ...

  4. 两种内存池管理方法对比

    一.问题背景 最近在调试ambiq apollo3的蓝牙时,其使用了ARM Cordio WSF的蓝牙协议栈.通过学习wsf_buf.c的实现,看到了一种不同于固定大小内存块的内存池管理方式.它使用了 ...

  5. JVM的四种内存屏障

    文章目录 1.为什么要有内存屏障 2.硬件上面的内存屏障 3.Java里面的四种内存屏障 4.使用内存屏障保存Volatile的有序性 4.1 单线程下的指令重排序 4.1 多线程下的指令重排序 1. ...

  6. 深入理解C语言-二级指针三种内存模型

    二级指针相对于一级指针,显得更难,难在于指针和数组的混合,定义不同类型的二级指针,在使用的时候有着很大的区别 第一种内存模型char *arr[] 若有如下定义 char *arr[] = {&quo ...

  7. 终端服务器有多种运行模式,云终端的三种工作模式你都知道的吗

    原标题:云终端的三种工作模式你都知道的吗 不知道大家有没有发现这几年来我们办公的电脑不知不觉中有笨重的台式机逐渐变成了小巧的云终端在使用的,我们去医院看病时也发现他们的办公的台式机也变成了云终端,甚至 ...

  8. CUDA下在Host端分配的几种内存模式

    CUDA下在Host端分配的几种内存模式 0条评论 2009-11-27 11:21   IT168网站原创 作者: 关鑫的博客 编辑: 覃里 [IT168 文档]Pageable VS Pinned ...

  9. c++内存管理-内存顺序

    转载自:https://www.dazhuanlan.com/2019/12/14/5df40323d6adf/ 原子操作(atomic)是无锁编程(Lock-Free Programming)的基础 ...

最新文章

  1. PHP TP5框架 安装运行 Warning: require(E:\phpstudy_pro\WWW\TP5\tp5\public/../thinkphp/base.php): failed to
  2. python使用matplotlib对比多个模型的在训练集上的效果并使用柱状图进行可视化:基于交叉验证的性能均值(mean)和标准差(std)进行可视化分析、使用标准差信息添加误差区间条yerr
  3. delphi如何让程序最小化到任务栏(使用Shell_NotifyIcon API函数)
  4. 原生CSS,实现点击按钮出现交互弹窗【新手扫盲】
  5. WatchOS系统开发大全(3)-创建第一个WatchApp工程
  6. 散列函数的应用及其安全性
  7. Java注解(1)-注解基础
  8. mysql插入性能测试
  9. HDU1753 大明A+B
  10. Linux特殊权限set_uid、set_gid、stick_bit命令和软链接文件、硬连接文件
  11. 服务器打微软补丁后无法启动,关于打了最新微软补丁后,针式打印机突然不好打印的处理方法。...
  12. ubuntu1804下txt文件乱码问题
  13. 从初级开发给大龄架构师review代码来看慢慢人生路
  14. 高盛报告:未来5-10年区块链将被广泛应用【附下载】
  15. PS零基础学习教程(一)
  16. input文本框与图片的对齐
  17. 杨幂生日祝福贺卡!!~
  18. linux xdm 启动执行文件,Linux下配置XDM登录服务器
  19. logit模型应用实例_广义线性模型应用举例之beta回归及R计算
  20. ristretto对cofactor1的椭圆曲线(如Curve25519等)的兼容(含Curve25519 cofactor的sage验证)

热门文章

  1. netbeans7.4_NetBeans 7.4的本机Java打包
  2. String#repeat来到Java吗?
  3. javaone_JavaOne 2012:Lambda之路
  4. 打破冷漠僵局文章_研究僵局–第1部分
  5. jetty 配置jndi_使用Jetty设置JNDI(嵌入式)
  6. hmac hmac.new_使用HMAC(Play 2.0)保护REST服务
  7. 端到端测试_端到端测试的滥用–测试技术2
  8. 快速的骆驼和云消息传递
  9. EE Servlet 3:简单表单处理
  10. Java验证(javafx)