内存栅栏 内存模型

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

相关文章

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

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

大数据小内存排序问题如何巧妙解决

《大数据小内存排序问题如何巧妙解决》文章介绍了大数据小内存排序的三种方法:数据库排序、分治法和位图法,数据库排序简单但速度慢,对设备要求高;分治法高效但实现复杂;位图法可读性差,但存储空间受限... 目录三种方法:方法概要数据库排序(http://www.chinasem.cn对数据库设备要求较高)分治法(常

Redis多种内存淘汰策略及配置技巧分享

《Redis多种内存淘汰策略及配置技巧分享》本文介绍了Redis内存满时的淘汰机制,包括内存淘汰机制的概念,Redis提供的8种淘汰策略(如noeviction、volatile-lru等)及其适用场... 目录前言一、什么是 Redis 的内存淘汰机制?二、Redis 内存淘汰策略1. pythonnoe

Java内存泄漏问题的排查、优化与最佳实践

《Java内存泄漏问题的排查、优化与最佳实践》在Java开发中,内存泄漏是一个常见且令人头疼的问题,内存泄漏指的是程序在运行过程中,已经不再使用的对象没有被及时释放,从而导致内存占用不断增加,最终... 目录引言1. 什么是内存泄漏?常见的内存泄漏情况2. 如何排查 Java 中的内存泄漏?2.1 使用 J

Golang的CSP模型简介(最新推荐)

《Golang的CSP模型简介(最新推荐)》Golang采用了CSP(CommunicatingSequentialProcesses,通信顺序进程)并发模型,通过goroutine和channe... 目录前言一、介绍1. 什么是 CSP 模型2. Goroutine3. Channel4. Channe

关于Java内存访问重排序的研究

《关于Java内存访问重排序的研究》文章主要介绍了重排序现象及其在多线程编程中的影响,包括内存可见性问题和Java内存模型中对重排序的规则... 目录什么是重排序重排序图解重排序实验as-if-serial语义内存访问重排序与内存可见性内存访问重排序与Java内存模型重排序示意表内存屏障内存屏障示意表Int

如何测试计算机的内存是否存在问题? 判断电脑内存故障的多种方法

《如何测试计算机的内存是否存在问题?判断电脑内存故障的多种方法》内存是电脑中非常重要的组件之一,如果内存出现故障,可能会导致电脑出现各种问题,如蓝屏、死机、程序崩溃等,如何判断内存是否出现故障呢?下... 如果你的电脑是崩溃、冻结还是不稳定,那么它的内存可能有问题。要进行检查,你可以使用Windows 11

Python基于火山引擎豆包大模型搭建QQ机器人详细教程(2024年最新)

《Python基于火山引擎豆包大模型搭建QQ机器人详细教程(2024年最新)》:本文主要介绍Python基于火山引擎豆包大模型搭建QQ机器人详细的相关资料,包括开通模型、配置APIKEY鉴权和SD... 目录豆包大模型概述开通模型付费安装 SDK 环境配置 API KEY 鉴权Ark 模型接口Prompt

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

NameNode内存生产配置

Hadoop2.x 系列,配置 NameNode 内存 NameNode 内存默认 2000m ,如果服务器内存 4G , NameNode 内存可以配置 3g 。在 hadoop-env.sh 文件中配置如下。 HADOOP_NAMENODE_OPTS=-Xmx3072m Hadoop3.x 系列,配置 Nam