C 的 6 种内存顺序,你都知道吗?

原子操作的内存顺序

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/339233.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

易语言 网页用什么编码_通常提到的编码器是干什么用的

编码器&#xff08;encoder&#xff09;是将信号&#xff08;如比特流&#xff09;或数据进行编制、转换为可用以通讯、传输和存储的信号形式的设备。编码器把角位移或直线位移转换成电信号&#xff0c;前者成为码盘&#xff0c;后者称码尺&#xff0e;按照读出方式编码器可以分…

如何优雅地实现 C 编译期静态反射

部门请来了软件专家袁英杰咨询师指导我们软件开发&#xff0c;从中我也学到了很多姿势&#xff0c;在此记录下来宝贵的经验。苹果的 mbp 品控真是差劲&#xff0c;写这个东西把 LShift 键 按坏了&#xff0c;真是难受。反射能做什么最近和大师聊软件设计&#xff0c;其中一个点…

香草 jboss 工具_如何为JBoss Developer Studio 8设置BPM和规则工具

香草 jboss 工具最新的JBoss Developer Studio&#xff08;JBDS&#xff09;的发布带来了有关如何开始使用尚未安装的各种JBoss Integration和BPM产品工具集的问题。 在本系列文章中&#xff0c;我们将为您概述如何安装每套工具并说明它们支持哪些产品。 这将有助于您在着手进…

局域网steam联机_适合和基友联机一起玩的单机游戏(1)

GTA5还有什么比在GTA中&#xff0c;和几个好基友一起&#xff0c;组建帮派&#xff0c;联机打砸抢&#xff0c;组队完成任务&#xff0c;和其他帮派火并更有意思的呢&#xff1f;游戏丰富的内容&#xff0c;各式各样的玩法&#xff0c;广袤的可探索空间&#xff0c;不愧是史上最…

C/C assert()函数用法总结与注意事项

1. 简介assert宏的原型定义在中&#xff0c;其作用是如果它的条件返回错误&#xff0c;则终止程序执行。原型定义&#xff1a;#include void assert( int expression );assert的作用是先计算表达式 expression &#xff0c;如果其值为假&#xff08;即为0&#xff09;&#xff…

ppt flash倒计时器_PPT三大神器之iSlide插件

本文约1200字&#xff0c;阅读预计需要4分钟。为了提升PPT制作效率&#xff0c;我们有必要使用一些插件来提升工作效率&#xff0c;而PPT有三大插件神器&#xff0c;分别是iSlide、PA口袋动画&#xff0c;Onekey Tool&#xff08;俗称OK插件&#xff09;&#xff0c;今天我们就…

C 语言中std::array的神奇用法总结

std::array是在C 11标准中增加的STL容器&#xff0c;它的设计目的是提供与原生数组类似的功能与性能。也正因此&#xff0c;使得std::array有很多与其他容器不同的特殊之处&#xff0c;比如&#xff1a;std::array的元素是直接存放在实例内部&#xff0c;而不是在堆上分配空间&…

java线程池并发_线程池之外:Java并发并不像您想象的那样糟糕

java线程池并发Apache Hadoop&#xff0c;Apache Spark&#xff0c;Akka&#xff0c;Java 8流和Quasar&#xff1a; 针对Java开发人员的经典用例以及最新的并发方法 关于并发性更新概念的讨论很多&#xff0c;但是许多开发人员还没有机会将他们的想法缠住。 在本文中&#xff…

网络营销理论模型_网络营销:课堂笔记(第四章下)

网络营销产品策略(续上篇)本章知识清单三、网络品牌如何打造&#xff1f;什么是品牌目前为止&#xff0c;对品牌的含义一直没有一个统一的、权威的解释。如果从品牌的构成要素和基本功能方面来界定品牌的话&#xff0c;最具有代表性和最经典的表述当属美国市场营销协会的定义。…

ios多线程Android,iOS 关于多线程

一.进程和线程1.什么是进程进程是指在系统中正在运行的一个应用程序每个进程之间是独立的&#xff0c;每个进程均运行在其专用且受保护的内存空间内比如&#xff1a;同时打开QQ&#xff0c;Xcode&#xff0c;系统就会分别启动2个进程通过”活动监视器”可以查看Mac系统中所开启…

websockets_使用用户名/密码和Servlet安全性保护WebSockets

websocketsRFC 6455提供了WebSockets安全注意事项的完整列表。 其中一些是在协议本身中烘焙的&#xff0c;其他一些则需要更多有关如何在特定服务器上实现它们的解释。 让我们来谈谈协议本身内置的一些安全性&#xff1a; HTTP请求中的Origin头仅包含标识发起该请求的主体&…

android横向排列 间隙,Android开发消除横向排列的多个Button之间的空隙

一.问题重述摘要里描述的可能不太清楚&#xff0c;问题如下图&#xff1a;如何消除Button1和Button2之间的空隙&#xff0c;以及Button与左右边界之间的空隙&#xff1f;二.问题根源这里出现的空隙其实是Button的背景图片中的透明部分&#xff0c;如下图&#xff1a;(两个按钮被…

电脑的发展史_互联网发展史 硅谷传奇之 IBM

2节 硅谷传奇之 IBM为什么要讲IBM呢&#xff1f;互联网是因计算机而诞生的&#xff0c;互联网的发展史与电脑的发展史有很多是重叠的&#xff0c;而IBM是上世纪60年代八大电脑公司之首。在互联网席卷全球之前&#xff0c;在硅谷是以无线电、军事技术、硅晶体管而闻名的。这些东…

android汉字田字格,画一个简单的田字格

image.png上代码package com.nick.customview;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.RectF;import android.support.annotation.Nullable;import android.sup…

xp定时关机软件_好用又免费的电脑定时工具,不用得后悔

现在利用电脑办公的人有多少&#xff0c;举个手示意下&#xff01;&#xff01;&#xff01;给电脑设置定时关机&#xff0c;可以方便我们不在电脑前完成关机操作。那么&#xff0c;如何设置定时关机呢&#xff1f;如果要取消&#xff0c;定时关机又如何取消&#xff1f;有的人…

apache camel_Apache Camel请向我解释这些端点选项的含义

apache camel在即将发布的Apache Camel 2.15中&#xff0c;我们使Camel更智能。 现在&#xff0c;它可以充当老师&#xff0c;并向您说明其配置方式以及这些选项的含义。 Camel可以做的第一课是告诉您如何配置所有端点以及这些选项的含义。 接下来我们要学习的课程是让Camel解…

android照片备份软件下载,照片备份云相册app下载-照片备份云相册下载V1.9安卓版-西西软件下载...

这款软件主要为安卓手机用户提供照片图片等备份的服务&#xff0c;通过照片备份云相册app可以轻松的为更多手机空间不够的用户清理出手机空间&#xff0c;照片图片等的是最不容易发现占用空间的&#xff0c;现在有了照片备份云相册app就能轻松让手机内存足够使用哦。照片备份云…

手机照片导入电脑步骤_如何将手机中的照片、视频快速的保存到U盘上?3分钟教你详细步骤...

手机拍照功能是越来越强大了。生活中&#xff0c;不少的朋友都喜欢拍照。拍着拍着不知不觉就存了许多的照片&#xff0c;占用很大的空间。手机照片和视频都是我们非常重要的回忆&#xff0c;许多人都不愿意删除。为了节省我们手机的空间&#xff0c;许多人都会选择将手机照片导…

筒仓计算表格_身份反模式:联邦筒仓和意大利面条身份

筒仓计算表格分析公司Quocirca的最新研究证实&#xff0c;现在许多企业的外部用户比内部用户更多&#xff1a;在欧洲&#xff0c;有58&#xff05;的企业直接与其他企业和/或消费者的用户进行交易&#xff1b; 仅在英国&#xff0c;这一数字就达到了65&#xff05;。 如果回顾历…

C/C 宏替换详解

1. 基本形式#define name replacement_text 复制代码通常情况下&#xff0c;#define 指令占一行&#xff0c;替换文本是 define 指令行尾部的所有剩余部分&#xff0c;但也可以把一个较长的宏定义分成若干行&#xff0c;这时需要在待续的行末尾加上一个反斜杠符 。宏定义也可以…