首页 > 后端开发 > C++ > 正文

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

P粉602998670
发布: 2025-08-24 12:02:01
原创
410人浏览过
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
登录后复制
(松散),不同的内存序提供了不同的性能和同步保证。选择合适的内存序是优化并发性能的关键,但也是最容易出错的地方。

居然设计家
居然设计家

居然之家和阿里巴巴共同打造的家居家装AI设计平台

居然设计家 199
查看详情 居然设计家

这些概念共同构成了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改进的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号