C++内存模型演进 C++11到C++20改进

C++11内存模型的核心是通过std::atomic和std::memory_order定义多线程下内存操作的可见性与顺序性,建立happens-before关系以避免数据竞争,确保程序正确性和可移植性。

c++内存模型演进 c++11到c++20改进

C++内存模型自C++11引入以来,为多线程编程提供了正式且跨平台的语义基础,极大地解决了此前在不同编译器和硬件平台上并发行为不一致的问题。从C++11奠定基石,到C++14、C++17的逐步完善,再到C++20带来实质性改进,整个演进过程都在努力平衡性能、易用性和正确性,特别是对原子操作的精细控制和低延迟同步机制的探索。

解决方案

C++内存模型的核心在于定义了多线程环境下,内存访问操作的可见性和顺序性。在C++11中,标准引入了

std::atomic

类型和

std::memory_order

枚举,这套机制旨在让开发者能够明确地控制内存操作的可见性,从而避免数据竞争和未定义行为。

C++11的奠基石:

std::atomic

是实现无锁编程的基石,它保证了对单个原子变量的读写操作是不可分割的。而

std::memory_order

则定义了这些原子操作与程序中其他内存操作之间的排序约束。

memory_order_relaxed

: 最宽松的内存序,只保证操作本身的原子性,不提供任何跨线程的排序保证。编译器和硬件可以随意重排。

memory_order_consume

: 消费顺序,保证依赖于原子操作结果的后续操作不会被重排到原子操作之前。这个曾被寄予厚望,但实际使用和实现都非常复杂,导致其在C++20中被弃用。

memory_order_acquire

: 获得顺序,保证此操作及之后的所有操作不会被重排到此操作之前。通常用于读取操作,确保能“看到”所有在此操作之前由

release

操作写入的数据。

memory_order_release

: 释放顺序,保证此操作及之前的所有操作不会被重排到此操作之后。通常用于写入操作,确保在此操作之前的所有写入对其他线程的

acquire

操作可见。

memory_order_acq_rel

: 获得-释放顺序,用于读-改-写操作(如

fetch_add

),同时具备

acquire

release

的语义。

memory_order_seq_cst

: 顺序一致性,最强的内存序。它不仅保证操作本身的原子性,还保证所有

seq_cst

操作在所有线程中都以相同的全局顺序发生。这是最容易理解和使用的,但通常也是性能开销最大的。

C++11通过“happens-before”关系来形式化这些内存序,确保了在并发环境下,不同线程的操作能够以可预测的方式相互影响。我个人觉得,C++11的这套东西,虽然初看复杂,但确实是多线程编程迈向标准化的关键一步。

立即学习“C++免费学习笔记(深入)”;

C++14和C++17的完善:C++14对内存模型更多是语法层面的小修小补和澄清,并没有引入新的核心概念。而C++17则开始触及性能优化的一些细节。它引入了

std::hardware_destructive_interference_size

std::hardware_constructive_interference_size

,这两个常量让开发者可以更好地理解CPU缓存行对并发性能的影响,从而在数据结构布局时进行优化,避免伪共享(false sharing)。这虽然不是直接修改内存模型本身,但它体现了标准委员会对并发性能细节的关注,以及如何通过对内存物理布局的理解来提升程序效率。说实话,这玩意儿对于写高性能并发代码的人来说,简直是福音,能省下不少摸索的时间。

C++20的重大改进:C++20在内存模型方面带来了几项重要的更新,旨在简化使用、提升性能和提供更强大的低级同步原语:

弃用

memory_order_consume

: 这是个大动作。由于

consume

的复杂性和难以正确实现,以及在实际应用中其性能优势并不明显,标准委员会最终决定弃用它。这意味着开发者不再需要纠结于这个难以捉摸的内存序,通常用

acquire

来替代其功能。我记得当时社区里关于

consume

的讨论就没有停过,弃用它也算是顺应民意了。

std::atomic_ref

: 这是一个非常实用的新特性。它允许你对一个现有的非原子对象创建一个原子视图,从而在局部范围内对其进行原子操作,而无需将原始对象声明为

std::atomic

。这对于改造遗留代码或者在某些场景下需要临时原子性访问非常有用。

std::atomic::wait

std::atomic::notify

: 这是一组低级同步原语,允许线程在原子变量上等待或被唤醒。它们通常比使用

std::mutex

std::condition_variable

更轻量级,因为它们不涉及操作系统的调度器,可以直接在用户空间进行等待和唤醒。这对于实现高性能的无锁数据结构或自定义同步机制提供了强大的工具

std::atomic_flag

的改进: 保证其是无锁的,并且提供了

test_and_set

clear

操作。

总的来说,从C++11到C++20,C++内存模型的发展方向是:在保持强大功能的同时,努力简化复杂性(如弃用

consume

),并提供更细粒度、更高性能的同步工具(如

atomic_ref

wait/notify

)。

C++11内存模型的核心概念是什么?

C++11内存模型的核心在于构建一个清晰的框架,来描述多线程程序中内存操作的可见性和顺序性,从而确保程序的正确性和可移植性。它主要围绕以下几个关键概念展开:

首先是数据竞争(Data Race)。这是C++内存模型要解决的首要问题。当两个或更多线程并发访问同一个内存位置,并且至少有一个是写入操作,同时这些访问之间没有适当的同步时,就会发生数据竞争。数据竞争会导致未定义行为(Undefined Behavior),这意味着程序可能崩溃、产生错误结果,或者表现出任何不可预测的行为。C++内存模型的目标之一就是提供工具,让开发者能够避免或正确处理数据竞争。

接着是happens-before关系。这是内存模型中最抽象但也是最重要的概念。它定义了两个操作之间的偏序关系,如果操作A happens-before 操作B,那么操作A对内存的修改对操作B是可见的。happens-before关系有几种来源:

sequenced-before: 同一线程内的操作顺序,这是最基本的。synchronizes-with: 通过原子操作(特别是

release

acquire

)或互斥量(

std::mutex

)等同步原语建立的跨线程关系。inter-thread happens-before: 通过一系列

synchronizes-with

sequenced-before

关系传递形成的复合关系。

理解happens-before关系是理解内存模型如何保证可见性和顺序性的关键。它告诉我们,哪些操作的执行顺序是确定的,以及一个线程对内存的修改何时能被另一个线程看到。

然后是原子操作(Atomic Operations)

std::atomic

模板类是C++内存模型提供的主要工具,用于执行原子操作。原子操作是不可分割的,这意味着它们要么完全执行,要么完全不执行,不会被其他线程的操作中断。这解决了单个变量读写操作的原子性问题。但仅仅原子性还不够,还需要控制这些原子操作与其他内存操作之间的排序,这就引出了

std::memory_order

最后是内存顺序(Memory Order)

std::memory_order

枚举定义了原子操作的内存排序语义。它告诉编译器和处理器在执行原子操作时,可以进行多大程度的指令重排。从最严格的

memory_order_seq_cst

(顺序一致性)到最宽松的

memory_order_relaxed

(松散),不同的内存序提供了不同的性能和同步保证。选择合适的内存序是优化并发性能的关键,但也是最容易出错的地方。

这些概念共同构成了C++11内存模型的基础,为编写正确、高效且可移植的并发代码提供了理论和实践的工具。

C++17和C++20对内存模型带来了哪些关键性改进?

C++17和C++20在C++11内存模型的基础上,进行了针对性的增强和优化,主要集中在提升开发体验、提供更高效的低级同步原语以及修正早期设计中的不足。

C++17最值得一提的,是引入了

std::hardware_destructive_interference_size

std::hardware_constructive_interference_size

。这并非直接修改内存模型的核心语义,但它直接关联到并发编程的性能优化。这两个常量提供了关于CPU缓存行大小的信息,帮助开发者在设计并发数据结构时,避免“伪共享”(false sharing)问题。伪共享发生在不同线程访问不同变量,但这些变量恰好位于同一个缓存行中时,导致缓存行在不同CPU核心间频繁无效化和同步,严重影响性能。通过了解缓存行大小,开发者可以有意地填充(padding)数据结构,确保并发访问的变量位于不同的缓存行,从而提升性能。这对我来说,是标准在向底层硬件特性靠拢,让C++开发者能写出更“硬核”的代码。

而C++20则带来了更实质性的内存模型改进,其中最引人注目的是:

首先是弃用

std::memory_order_consume

。这是一个重大决策。

memory_order_consume

的本意是提供比

acquire

更弱但仍能保证依赖链可见性的内存序,以期获得更好的性能。然而,它的语义过于复杂,难以正确理解和实现,导致实际应用中开发者往往难以驾驭,并且编译器在实现上也面临挑战,实际性能提升也并不明显。因此,标准委员会决定将其弃用,建议开发者在需要类似语义时,直接使用

memory_order_acquire

,这无疑简化了内存模型的使用复杂性,减少了潜在的错误。

其次,

std::atomic_ref

的引入。这是一个非常实用的特性。过去,如果你想对一个非

std::atomic

类型的对象进行原子操作,你可能需要用

std::mutex

来保护,或者干脆把对象声明为

std::atomic

。但

std::atomic_ref

允许你在运行时,对任何可原子操作的类型(如

int

,

bool

, 指针等)创建一个原子视图。这意味着你可以对一个普通的

int

变量进行

fetch_add

操作,而不需要改变其原始类型。这对于集成遗留代码、或者在某些特定场景下需要临时原子性访问的场景,提供了极大的便利和灵活性。

再者,

std::atomic::wait

std::atomic::notify

方法的加入。这组方法为原子类型提供了低级的等待和通知机制,类似于Linux的futex(fast user-space mutexes)。它们允许一个线程在原子变量的值满足某个条件时进行等待,或者被另一个线程唤醒。与传统的

std::mutex

std::condition_variable

相比,

atomic::wait/notify

通常更轻量级,因为它们可以在用户空间完成,避免了昂贵的内核上下文切换,从而在某些高并发、低延迟的场景下提供更好的性能。这对于构建自定义的无锁数据结构或高效的同步原语来说,无疑是如虎添翼。

这些改进共同体现了C++标准委员会在内存模型演进上的思考:既要保证正确性,又要兼顾性能,同时还要尽可能地降低使用门槛。

如何选择合适的内存顺序(memory_order)以优化并发性能?

选择合适的

memory_order

是C++并发编程中既关键又容易出错的一环。它直接关系到程序的正确性、可移植性以及性能。我的经验是,永远从最简单的开始,然后按需优化。

1.

memory_order_seq_cst

(顺序一致性):这是最简单、最直观的选择。它提供了所有线程中操作的全局总顺序,这意味着所有线程看到的原子操作序列都是一致的。如果你对内存模型不熟悉,或者对特定并发场景的同步需求不确定,优先使用

seq_cst

。它能最大程度地保证程序的正确性,因为它提供了最强的同步保证。然而,这种强保证通常伴随着最高的性能开销,因为它可能需要在硬件层面进行昂贵的全局同步(例如,在x86上,通常会使用

lock

前缀指令或隐式内存屏障)。我个人觉得,对于大多数非极端性能要求的场景,或者你正在调试一个并发问题时,

seq_cst

是你的安全港。只有当你通过性能分析工具确定

seq_cst

是瓶颈时,才考虑使用更弱的内存序。

2.

memory_order_acquire

memory_order_release

(获取-释放顺序):这对是实现“生产者-消费者”模型或更复杂同步模式的常用组合。

memory_order_release

用于生产者写入数据并释放信号。它保证在

release

操作之前的所有内存写入操作,对之后执行

acquire

操作的线程是可见的。可以想象成一个“写屏障”,确保之前的写入都已完成并对其他线程可见。

memory_order_acquire

用于消费者读取信号并获取数据。它保证在

acquire

操作之后的所有内存读取操作,能看到在对应的

release

操作之前完成的所有写入。可以想象成一个“读屏障”,确保能看到其他线程的最新数据。这种组合提供了比

seq_cst

更弱但仍非常强大的同步保证,且通常性能优于

seq_cst

,因为它只在特定的同步点提供排序保证,而不是全局排序。这是我最常使用的内存序组合,因为它能满足绝大多数的同步需求,同时提供了不错的性能。

3.

memory_order_acq_rel

(获取-释放顺序):这个用于读-改-写(RMW)操作,例如

fetch_add

compare_exchange_weak

,当这些操作既需要获取最新值,又需要将新值释放给其他线程时使用。它结合了

acquire

release

的语义,保证了RMW操作既能看到之前

release

操作写入的数据,又能将其自身及之前的修改对后续

acquire

操作可见。这比单独的

acquire

release

更复杂,但对于某些原子更新操作来说是必要的。

4.

memory_order_relaxed

(松散顺序):这是最宽松的内存序,只保证操作本身的原子性,不提供任何跨线程的排序保证。这意味着编译器和硬件可以随意重排

relaxed

操作与其他内存操作的顺序。因此,它通常性能最好,但也是最难正确使用的。何时使用? 仅当你确定你只需要原子性,而不需要任何排序保证时。例如,一个简单的计数器,你只关心最终的计数结果,而不关心中间的递增顺序。或者,一个统计变量,它的值可能在多个线程中被修改,但你只关心它的近似值,不关心修改的精确顺序。如果你不确定,就不要用它。我见过太多因为滥用

relaxed

而导致难以追踪的并发bug。

选择策略总结:

seq_cst

开始。 确保功能正确。性能瓶颈分析。 如果

seq_cst

成为性能瓶颈,并且你确定需要优化,再考虑使用更弱的内存序。理解并发模式。 生产者-消费者模式通常用

acquire

/

release

避免

consume

C++20已经弃用,即使在旧标准中也应尽量避免。测试!测试!测试! 并发代码的bug是出了名的难以复现和调试。使用各种并发测试工具和方法来验证你的内存序选择。

记住,过度的优化往往是万恶之源。在并发编程中,正确性永远排在性能之前。

以上就是C++内存模型演进 C++11到C++20改进的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1472720.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月18日 19:50:54
下一篇 2025年12月18日 19:51:13

相关推荐

  • Uniapp 中如何不拉伸不裁剪地展示图片?

    灵活展示图片:如何不拉伸不裁剪 在界面设计中,常常需要以原尺寸展示用户上传的图片。本文将介绍一种在 uniapp 框架中实现该功能的简单方法。 对于不同尺寸的图片,可以采用以下处理方式: 极端宽高比:撑满屏幕宽度或高度,再等比缩放居中。非极端宽高比:居中显示,若能撑满则撑满。 然而,如果需要不拉伸不…

    2025年12月24日
    400
  • 如何让小说网站控制台显示乱码,同时网页内容正常显示?

    如何在不影响用户界面的情况下实现控制台乱码? 当在小说网站上下载小说时,大家可能会遇到一个问题:网站上的文本在网页内正常显示,但是在控制台中却是乱码。如何实现此类操作,从而在不影响用户界面(UI)的情况下保持控制台乱码呢? 答案在于使用自定义字体。网站可以通过在服务器端配置自定义字体,并通过在客户端…

    2025年12月24日
    800
  • SASS 中的 Mixins

    mixin 是 css 预处理器提供的工具,虽然它们不是可以被理解的函数,但它们的主要用途是重用代码。 不止一次,我们需要创建多个类来执行相同的操作,但更改单个值,例如字体大小的多个类。 .fs-10 { font-size: 10px;}.fs-20 { font-size: 20px;}.fs-…

    2025年12月24日
    000
  • 如何在地图上轻松创建气泡信息框?

    地图上气泡信息框的巧妙生成 地图上气泡信息框是一种常用的交互功能,它简便易用,能够为用户提供额外信息。本文将探讨如何借助地图库的功能轻松创建这一功能。 利用地图库的原生功能 大多数地图库,如高德地图,都提供了现成的信息窗体和右键菜单功能。这些功能可以通过以下途径实现: 高德地图 JS API 参考文…

    2025年12月24日
    400
  • 如何使用 scroll-behavior 属性实现元素scrollLeft变化时的平滑动画?

    如何实现元素scrollleft变化时的平滑动画效果? 在许多网页应用中,滚动容器的水平滚动条(scrollleft)需要频繁使用。为了让滚动动作更加自然,你希望给scrollleft的变化添加动画效果。 解决方案:scroll-behavior 属性 要实现scrollleft变化时的平滑动画效果…

    2025年12月24日
    000
  • 如何为滚动元素添加平滑过渡,使滚动条滑动时更自然流畅?

    给滚动元素平滑过渡 如何在滚动条属性(scrollleft)发生改变时为元素添加平滑的过渡效果? 解决方案:scroll-behavior 属性 为滚动容器设置 scroll-behavior 属性可以实现平滑滚动。 html 代码: click the button to slide right!…

    2025年12月24日
    500
  • 如何选择元素个数不固定的指定类名子元素?

    灵活选择元素个数不固定的指定类名子元素 在网页布局中,有时需要选择特定类名的子元素,但这些元素的数量并不固定。例如,下面这段 html 代码中,activebar 和 item 元素的数量均不固定: *n *n 如果需要选择第一个 item元素,可以使用 css 选择器 :nth-child()。该…

    2025年12月24日
    200
  • 使用 SVG 如何实现自定义宽度、间距和半径的虚线边框?

    使用 svg 实现自定义虚线边框 如何实现一个具有自定义宽度、间距和半径的虚线边框是一个常见的前端开发问题。传统的解决方案通常涉及使用 border-image 引入切片图片,但是这种方法存在引入外部资源、性能低下的缺点。 为了避免上述问题,可以使用 svg(可缩放矢量图形)来创建纯代码实现。一种方…

    2025年12月24日
    100
  • 如何让“元素跟随文本高度,而不是撑高父容器?

    如何让 元素跟随文本高度,而不是撑高父容器 在页面布局中,经常遇到父容器高度被子元素撑开的问题。在图例所示的案例中,父容器被较高的图片撑开,而文本的高度没有被考虑。本问答将提供纯css解决方案,让图片跟随文本高度,确保父容器的高度不会被图片影响。 解决方法 为了解决这个问题,需要将图片从文档流中脱离…

    2025年12月24日
    000
  • 为什么 CSS mask 属性未请求指定图片?

    解决 css mask 属性未请求图片的问题 在使用 css mask 属性时,指定了图片地址,但网络面板显示未请求获取该图片,这可能是由于浏览器兼容性问题造成的。 问题 如下代码所示: 立即学习“前端免费学习笔记(深入)”; icon [data-icon=”cloud”] { –icon-cl…

    2025年12月24日
    200
  • 如何利用 CSS 选中激活标签并影响相邻元素的样式?

    如何利用 css 选中激活标签并影响相邻元素? 为了实现激活标签影响相邻元素的样式需求,可以通过 :has 选择器来实现。以下是如何具体操作: 对于激活标签相邻后的元素,可以在 css 中使用以下代码进行设置: li:has(+li.active) { border-radius: 0 0 10px…

    2025年12月24日
    100
  • 如何模拟Windows 10 设置界面中的鼠标悬浮放大效果?

    win10设置界面的鼠标移动显示周边的样式(探照灯效果)的实现方式 在windows设置界面的鼠标悬浮效果中,光标周围会显示一个放大区域。在前端开发中,可以通过多种方式实现类似的效果。 使用css 使用css的transform和box-shadow属性。通过将transform: scale(1.…

    2025年12月24日
    200
  • 为什么我的 Safari 自定义样式表在百度页面上失效了?

    为什么在 Safari 中自定义样式表未能正常工作? 在 Safari 的偏好设置中设置自定义样式表后,您对其进行测试却发现效果不同。在您自己的网页中,样式有效,而在百度页面中却失效。 造成这种情况的原因是,第一个访问的项目使用了文件协议,可以访问本地目录中的图片文件。而第二个访问的百度使用了 ht…

    2025年12月24日
    000
  • 如何用前端实现 Windows 10 设置界面的鼠标移动探照灯效果?

    如何在前端实现 Windows 10 设置界面中的鼠标移动探照灯效果 想要在前端开发中实现 Windows 10 设置界面中类似的鼠标移动探照灯效果,可以通过以下途径: CSS 解决方案 DEMO 1: Windows 10 网格悬停效果:https://codepen.io/tr4553r7/pe…

    2025年12月24日
    000
  • 使用CSS mask属性指定图片URL时,为什么浏览器无法加载图片?

    css mask属性未能加载图片的解决方法 使用css mask属性指定图片url时,如示例中所示: mask: url(“https://api.iconify.design/mdi:apple-icloud.svg”) center / contain no-repeat; 但是,在网络面板中却…

    2025年12月24日
    000
  • 如何用CSS Paint API为网页元素添加时尚的斑马线边框?

    为元素添加时尚的斑马线边框 在网页设计中,有时我们需要添加时尚的边框来提升元素的视觉效果。其中,斑马线边框是一种既醒目又别致的设计元素。 实现斜向斑马线边框 要实现斜向斑马线间隔圆环,我们可以使用css paint api。该api提供了强大的功能,可以让我们在元素上绘制复杂的图形。 立即学习“前端…

    2025年12月24日
    000
  • 图片如何不撑高父容器?

    如何让图片不撑高父容器? 当父容器包含不同高度的子元素时,父容器的高度通常会被最高元素撑开。如果你希望父容器的高度由文本内容撑开,避免图片对其产生影响,可以通过以下 css 解决方法: 绝对定位元素: .child-image { position: absolute; top: 0; left: …

    2025年12月24日
    000
  • CSS 帮助

    我正在尝试将文本附加到棕色框的左侧。我不能。我不知道代码有什么问题。请帮助我。 css .hero { position: relative; bottom: 80px; display: flex; justify-content: left; align-items: start; color:…

    2025年12月24日 好文分享
    200
  • 前端代码辅助工具:如何选择最可靠的AI工具?

    前端代码辅助工具:可靠性探讨 对于前端工程师来说,在HTML、CSS和JavaScript开发中借助AI工具是司空见惯的事情。然而,并非所有工具都能提供同等的可靠性。 个性化需求 关于哪个AI工具最可靠,这个问题没有一刀切的答案。每个人的使用习惯和项目需求各不相同。以下是一些影响选择的重要因素: 立…

    2025年12月24日
    000
  • 如何用 CSS Paint API 实现倾斜的斑马线间隔圆环?

    实现斑马线边框样式:探究 css paint api 本文将探究如何使用 css paint api 实现倾斜的斑马线间隔圆环。 问题: 给定一个有多个圆圈组成的斑马线图案,如何使用 css 实现倾斜的斑马线间隔圆环? 答案: 立即学习“前端免费学习笔记(深入)”; 使用 css paint api…

    2025年12月24日
    000

发表回复

登录后才能评论
关注微信