内存栅栏 内存模型

2024-04-20 17:52
文章标签 内存 模型 栅栏

本文主要是介绍内存栅栏 内存模型,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

公众号:内存栅栏

先抛出代码:

atomic<int> a{0};atomic<int> b{0};int set(){    a.store(1,memory_order_relax);    b.store(2,memory_order_relax);int observe(){cout<<"a="a<<" b="<<b;}int main(){thead t1(set);thread t2(observe);t1.join();t2.join();}

问题:上面的代码, a和b的写操作, 顺序是怎样的?observe输出是什么?

仔细分析下,就能得出下面的几种情况:

  1. a=0 b =0

  2. a=1 b=2

  3. a=1 b=0

  4. a=0 b=2

   前三种情况都好理解,但是a=0 b=2这种情况,是不是感觉不好理解?为什么会出现a=0 b=2这种看起来不太合理的情况?这就涉及到内存栅栏了,内存模型的一致性问题。

我们都知道代码编译的时候,编译器会对代码进行优化,如gcc编译参数 :

  • -O0表示不做优化

  • -O1为默认优化

  • -O2除了10的优化以外,还做一些额外调整工作,例如指令调整等

  • -O3包括循环展开和其他一些处理特性相关的优化工作

    是的,编译器会对代码进行优化,如果编译器认定a、b的赋值操作语句的执行顺序对结果无任何影响的话,则可以根据情况将指令进行重排序以提高性能。如果a、b的赋值语句的执行必须是先a后b,则编译器不会进行优化。    除了编译器,甚至处理器也可能对指令的执行顺序打乱(通常这样的执行顺序都是超标量的流水线,即一个时钟周期内发射多条指令产生的),通常称为弱顺序(weak orderd)。这种情况下,b的赋值也完全可能在a赋值之前完成。当然,这和处理器架构有关,像我们平常用到的x86和SPARC都是强顺序内存模型,而采用Alpha、PowerPC、Itanlium、ArmV7这样的平台,都是弱顺序内存模型。

所以,上面的例子中,出现a=0, b=2就不足为奇了。一般情况下,出现这样的问题,程序员也很难想象是编译器或者处理器改变了代码的执行顺序导致错误的。

那么如何确保开启编译器优化的情况下,保证并发环境下,内存访问的正确性和一致性呢?幸运的是,我们有内存栅栏。

内存栅栏(Memory Barrier),之所以被称为“栅栏”,是因为它们在执行流中起到了隔离的作用,类似于现实生活中栅栏的功能,阻止某些事物通过。在计算机科学中,内存栅栏阻止指令重排越过这一“栅栏”,确保在栅栏一侧的操作(无论是读操作还是写操作)在逻辑上完全完成后,才能开始执行栅栏另一侧的操作。

1. 作用  

    ​​​​​​​​​​​​​​防止编译器和处理器进行过度的指令重排,确保在并发环境下内存访问的正确性和一致性。

在多处理器系统中,每个处理器可能有自己的缓存。处理器为了提高执行效率,会在执行程序时对指令进行重排。编译器同样也会在生成机器码时进行优化和指令重排。这些重排优化虽然提高了单线程代码的效率,但在多线程环境中可能导致数据不一致的问题。内存栅栏的引入,就是为了解决由于指令重排带来的内存可见性问题。

2. 类型

内存栅栏主要有以下几种类型:

  1. Load Barrier(加载栅栏):确保所有在栅栏之前的读操作完成后,才能执行栅栏之后的读操作。

  2. Store Barrier(存储栅栏):确保所有在栅栏之前的写操作完成后,才能执行栅栏之后的写操作。

  3. Full Barrier(全栅栏):结合加载栅栏和存储栅栏的功能,确保所有在栅栏之前的读写操作完成后,才能执行栅栏之后的读写操作。

3. C++中的内存栅栏

C++11标准引入了原子操作和内存模型的概念,其中就包括对内存栅栏的支持。C++提供的内存栅栏是通过原子操作库中的内存顺序参数来实现的:

  1. std::memory_order_relaxed:无同步或顺序制约。

  2. std::memory_order_acquire:本线程中,所有后续的读操作都必须在本原子操作完成后执行。

  3. std::memory_order_release:本线程中,所有之前的写操作完成后才能执行本原子操作。

  4. std::memory_order_acq_rel:同时具有acquire和release的效果。

  5. std::memory_order_consume: 本线程中,所有后续的有关本原子操作,必须在本原子操作完成后执行。

  6. std::memory_order_seq_cst:全栅栏,提供顺序一致的内存顺序。
     

那么上面的例子,如何确保赋值a一定在赋值b之前呢?

atomic<int> a{0};atomic<int> b{0};int set(){    a.store(1,memory_order_relax);    b.store(2,memory_order_release);//所有的写操作完成,才能执行本操作} int observe(){cout<<"a="a<<" b="<<b;}int main(){thead t1(set);thread t2(observe);t1.join();t2.join();}

结论

内存栅栏是确保多线程程序在现代多处理器系统中正确运行的关键机制。C++通过原子操作和明确的内存顺序模型提供了强大的工具来处理这一问题,开发者需要根据具体需求合理选择使用。

这篇关于内存栅栏 内存模型的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/920942

相关文章

SpringBoot快速接入OpenAI大模型的方法(JDK8)

《SpringBoot快速接入OpenAI大模型的方法(JDK8)》本文介绍了如何使用AI4J快速接入OpenAI大模型,并展示了如何实现流式与非流式的输出,以及对函数调用的使用,AI4J支持JDK8... 目录使用AI4J快速接入OpenAI大模型介绍AI4J-github快速使用创建SpringBoot

golang内存对齐的项目实践

《golang内存对齐的项目实践》本文主要介绍了golang内存对齐的项目实践,内存对齐不仅有助于提高内存访问效率,还确保了与硬件接口的兼容性,是Go语言编程中不可忽视的重要优化手段,下面就来介绍一下... 目录一、结构体中的字段顺序与内存对齐二、内存对齐的原理与规则三、调整结构体字段顺序优化内存对齐四、内

0基础租个硬件玩deepseek,蓝耘元生代智算云|本地部署DeepSeek R1模型的操作流程

《0基础租个硬件玩deepseek,蓝耘元生代智算云|本地部署DeepSeekR1模型的操作流程》DeepSeekR1模型凭借其强大的自然语言处理能力,在未来具有广阔的应用前景,有望在多个领域发... 目录0基础租个硬件玩deepseek,蓝耘元生代智算云|本地部署DeepSeek R1模型,3步搞定一个应

Deepseek R1模型本地化部署+API接口调用详细教程(释放AI生产力)

《DeepseekR1模型本地化部署+API接口调用详细教程(释放AI生产力)》本文介绍了本地部署DeepSeekR1模型和通过API调用将其集成到VSCode中的过程,作者详细步骤展示了如何下载和... 目录前言一、deepseek R1模型与chatGPT o1系列模型对比二、本地部署步骤1.安装oll

Spring AI Alibaba接入大模型时的依赖问题小结

《SpringAIAlibaba接入大模型时的依赖问题小结》文章介绍了如何在pom.xml文件中配置SpringAIAlibaba依赖,并提供了一个示例pom.xml文件,同时,建议将Maven仓... 目录(一)pom.XML文件:(二)application.yml配置文件(一)pom.xml文件:首

如何在本地部署 DeepSeek Janus Pro 文生图大模型

《如何在本地部署DeepSeekJanusPro文生图大模型》DeepSeekJanusPro模型在本地成功部署,支持图片理解和文生图功能,通过Gradio界面进行交互,展示了其强大的多模态处... 目录什么是 Janus Pro1. 安装 conda2. 创建 python 虚拟环境3. 克隆 janus

本地私有化部署DeepSeek模型的详细教程

《本地私有化部署DeepSeek模型的详细教程》DeepSeek模型是一种强大的语言模型,本地私有化部署可以让用户在自己的环境中安全、高效地使用该模型,避免数据传输到外部带来的安全风险,同时也能根据自... 目录一、引言二、环境准备(一)硬件要求(二)软件要求(三)创建虚拟环境三、安装依赖库四、获取 Dee

DeepSeek模型本地部署的详细教程

《DeepSeek模型本地部署的详细教程》DeepSeek作为一款开源且性能强大的大语言模型,提供了灵活的本地部署方案,让用户能够在本地环境中高效运行模型,同时保护数据隐私,在本地成功部署DeepSe... 目录一、环境准备(一)硬件需求(二)软件依赖二、安装Ollama三、下载并部署DeepSeek模型选

Linux内存泄露的原因排查和解决方案(内存管理方法)

《Linux内存泄露的原因排查和解决方案(内存管理方法)》文章主要介绍了运维团队在Linux处理LB服务内存暴涨、内存报警问题的过程,从发现问题、排查原因到制定解决方案,并从中学习了Linux内存管理... 目录一、问题二、排查过程三、解决方案四、内存管理方法1)linux内存寻址2)Linux分页机制3)

Java循环创建对象内存溢出的解决方法

《Java循环创建对象内存溢出的解决方法》在Java中,如果在循环中不当地创建大量对象而不及时释放内存,很容易导致内存溢出(OutOfMemoryError),所以本文给大家介绍了Java循环创建对象... 目录问题1. 解决方案2. 示例代码2.1 原始版本(可能导致内存溢出)2.2 修改后的版本问题在