内存栅栏 内存模型

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

相关文章

Python如何使用__slots__实现节省内存和性能优化

《Python如何使用__slots__实现节省内存和性能优化》你有想过,一个小小的__slots__能让你的Python类内存消耗直接减半吗,没错,今天咱们要聊的就是这个让人眼前一亮的技巧,感兴趣的... 目录背景:内存吃得满满的类__slots__:你的内存管理小助手举个大概的例子:看看效果如何?1.

Java的IO模型、Netty原理解析

《Java的IO模型、Netty原理解析》Java的I/O是以流的方式进行数据输入输出的,Java的类库涉及很多领域的IO内容:标准的输入输出,文件的操作、网络上的数据传输流、字符串流、对象流等,这篇... 目录1.什么是IO2.同步与异步、阻塞与非阻塞3.三种IO模型BIO(blocking I/O)NI

基于Flask框架添加多个AI模型的API并进行交互

《基于Flask框架添加多个AI模型的API并进行交互》:本文主要介绍如何基于Flask框架开发AI模型API管理系统,允许用户添加、删除不同AI模型的API密钥,感兴趣的可以了解下... 目录1. 概述2. 后端代码说明2.1 依赖库导入2.2 应用初始化2.3 API 存储字典2.4 路由函数2.5 应

Redis 内存淘汰策略深度解析(最新推荐)

《Redis内存淘汰策略深度解析(最新推荐)》本文详细探讨了Redis的内存淘汰策略、实现原理、适用场景及最佳实践,介绍了八种内存淘汰策略,包括noeviction、LRU、LFU、TTL、Rand... 目录一、 内存淘汰策略概述二、内存淘汰策略详解2.1 ​noeviction(不淘汰)​2.2 ​LR

Golang基于内存的键值存储缓存库go-cache

《Golang基于内存的键值存储缓存库go-cache》go-cache是一个内存中的key:valuestore/cache库,适用于单机应用程序,本文主要介绍了Golang基于内存的键值存储缓存库... 目录文档安装方法示例1示例2使用注意点优点缺点go-cache 和 Redis 缓存对比1)功能特性

C#集成DeepSeek模型实现AI私有化的流程步骤(本地部署与API调用教程)

《C#集成DeepSeek模型实现AI私有化的流程步骤(本地部署与API调用教程)》本文主要介绍了C#集成DeepSeek模型实现AI私有化的方法,包括搭建基础环境,如安装Ollama和下载DeepS... 目录前言搭建基础环境1、安装 Ollama2、下载 DeepSeek R1 模型客户端 ChatBo

Go使用pprof进行CPU,内存和阻塞情况分析

《Go使用pprof进行CPU,内存和阻塞情况分析》Go语言提供了强大的pprof工具,用于分析CPU、内存、Goroutine阻塞等性能问题,帮助开发者优化程序,提高运行效率,下面我们就来深入了解下... 目录1. pprof 介绍2. 快速上手:启用 pprof3. CPU Profiling:分析 C

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步搞定一个应