C++多线程中怎样避免虚假共享 缓存行填充技术

虚假共享是指多个线程修改位于同一缓存行中的不同变量,导致缓存频繁失效,从而降低性能;其解决方法包括使用缓存行填充、alignas对齐、标准库常量或宏定义缓存行大小,确保每个线程访问的变量独占一个缓存行,尽管增加内存开销,但在高并发场景下性能提升显著。

C++多线程中怎样避免虚假共享 缓存行填充技术

C++多线程编程中,虚假共享(False Sharing) 是一个容易被忽视但严重影响性能的问题。它发生在多个线程修改不同的变量,而这些变量恰好位于同一个缓存行(Cache Line)中,导致CPU缓存频繁失效,从而降低程序性能。

什么是虚假共享?

现代CPU为了提高访问速度,将内存按缓存行(通常为64字节)为单位加载到各级缓存中。当一个核心修改了某个变量,该变量所在的整个缓存行会被标记为“已修改”,其他核心中该缓存行的副本就会失效,需要重新从内存或其他核心同步。

如果两个线程分别修改位于同一缓存行中的不同变量,即使变量之间毫无关系,也会因为共享缓存行而频繁触发缓存同步,这就是虚假共享

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

如何避免虚假共享?——缓存行填充

最常用的方法是缓存行填充(Cache Line Padding),即通过在变量之间插入足够的填充字节,确保每个线程独占一个缓存行。

方法一:手动填充结构体

假设你有一个结构体,多个线程会频繁修改其中的成员:

struct Counter {    alignas(64) int64_t value;  // 对齐到缓存行起始    char padding[64 - sizeof(int64_t)];  // 填充到64字节};

但更常见的是多个计数器并列的情况:

struct PaddedCounter {    int64_t value;    char padding[64 - sizeof(int64_t)];  // 填充至64字节};PaddedCounter counters[4];  // 每个计数器独占一个缓存行

这样,每个

value

都位于独立的缓存行中,线程修改各自的计数器时不会影响其他线程的缓存。

方法二:使用

alignas

和结构体对齐

C++11 提供了

alignas

关键字,可以强制变量对齐到特定边界:

struct alignas(64) CounterAligned {    int64_t value;};

这样,每个

CounterAligned

实例都会按64字节对齐,只要不连续存放多个实例在同一个缓存行内,就能避免共享。

但注意:如果数组中连续存放多个

CounterAligned

,且每个只占8字节,那么一个缓存行仍可容纳8个对象。因此还需结合填充:

struct NoFalseSharing {    int64_t value;    alignas(64) char pad;  // 下一个变量会从新的缓存行开始};

或者更清晰地:

struct NoFalseSharing {    int64_t value;    char padding[64 - sizeof(int64_t)];} alignas(64);

方法三:使用标准库或宏定义缓存行大小

缓存行大小在不同平台可能不同(x86_64通常是64字节),可定义宏:

#if defined(__cpp_lib_hardware_interference_size)    using std::hardware_destructive_interference_size;    using std::hardware_constructive_interference_size;#else    // 多数平台为64字节    constexpr size_t hardware_destructive_interference_size = 64;#endifstruct Counter {    int64_t value;    char padding[hardware_destructive_interference_size - sizeof(int64_t)];};

C++17 起引入了

std::hardware_destructive_interference_size

,专用于避免虚假共享。

注意:该常量在 C++17 中被引入,但在 C++20 中被移除,因为实现困难。许多编译器仍支持,或可通过宏定义模拟。

实际例子:多个线程更新独立计数器

#include #include #include struct BadCase {    int64_t a, b;  // 在同一缓存行,易发生虚假共享};struct GoodCase {    int64_t a;    char padding[64 - sizeof(int64_t)];    int64_t b;};int main() {    const int n = 10000000;    // 测试 BadCase    {        BadCase c{0, 0};        auto start = std::chrono::high_resolution_clock::now();        std::thread t1([&]{ for (int i = 0; i < n; ++i) c.a++; });        std::thread t2([&]{ for (int i = 0; i < n; ++i) c.b++; });        t1.join(); t2.join();        auto end = std::chrono::high_resolution_clock::now();        auto ms = std::chrono::duration_cast(end - start);        printf("BadCase: %lld msn", ms.count());    }    // 测试 GoodCase    {        GoodCase c{0, {}, 0};        auto start = std::chrono::high_resolution_clock::now();        std::thread t1([&]{ for (int i = 0; i < n; ++i) c.a++; });        std::thread t2([&]{ for (int i = 0; i < n; ++i) c.b++; });        t1.join(); t2.join();        auto end = std::chrono::high_resolution_clock::now();        auto ms = std::chrono::duration_cast(end - start);        printf("GoodCase: %lld msn", ms.count());    }    return 0;}

在多核系统上,

GoodCase

通常比

BadCase

快数倍。

其他建议

避免在循环中频繁访问跨线程共享的紧密变量优先使用线程局部存储(TLS)或局部变量累加,最后合并结果对于数组,可考虑每个线程使用独立的槽位(如按线程ID索引),并确保槽位间隔至少一个缓存行

例如:

alignas(64) int64_t local_sum[std::thread::hardware_concurrency()];

总结

避免虚假共享的关键是:

理解缓存行的工作机制(通常是64字节)使用填充或对齐确保不同线程访问的变量不在同一缓存行优先使用

alignas(64)

和填充数组在性能敏感的并发计数、状态标志等场景特别注意

虽然填充会增加内存占用,但在高并发场景下,性能提升通常远超内存开销。

基本上就这些,不复杂但容易忽略。

以上就是C++多线程中怎样避免虚假共享 缓存行填充技术的详细内容,更多请关注创想鸟其它相关文章!

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

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月18日 18:35:05
下一篇 2025年12月18日 18:35:13

相关推荐

  • enable_shared_from_this何时使用 获取this的shared_ptr方法

    当需要在类内部安全获取指向当前对象的std::shared_ptr时应使用std::enable_shared_from_this,因为直接使用std::shared_ptr(this)会创建独立的引用计数导致双重释放;正确做法是让类继承std::enable_shared_from_this并通过…

    2025年12月18日
    000
  • C++模板元编程是什么 编译期计算入门示例

    c++++模板元编程(tmp)是一种在编译期进行计算和逻辑处理的技术,其核心在于利用模板机制让编译器在编译阶段完成如数学运算、类型判断等任务。1. 它通过模板参数传递信息,2. 使用递归和特化实现逻辑控制,3. 所有结果在编译时即已确定,4. 常用于类型萃取、编译期数值计算、条件分支模拟、静态断言及…

    2025年12月18日 好文分享
    000
  • 如何理解C++20的coroutine特性 协程在异步编程中的应用

    c++++20协程通过提供co_await、co_yield和co_return关键字简化异步编程,使异步代码具备同步写法的清晰逻辑。1. co_await用于暂停协程并等待异步操作完成,避免阻塞线程;2. co_yield支持生成器模式,产出值后暂停;3. co_return用于返回结果或结束协程…

    2025年12月18日 好文分享
    000
  • C++中如何定义变量 基本数据类型与声明语法详解

    c++++中常见的基本数据类型包括整型(如int、short、long、long long,用于存储不同范围的整数,可加unsigned表示无符号)、浮点型(float、double、long double,用于存储小数,精度依次升高)、字符型(char,用于存储单个字符或小整数)、布尔型(bool…

    2025年12月18日
    000
  • C++中如何避免数组指针的内存泄漏 RAII管理动态数组

    在c++++中,为避免动态数组内存泄漏,应使用raii机制管理资源。1. 使用 std::unique_ptr 或 std::shared_ptr 自动释放数组内存,确保独占或共享所有权下的正确析构;2. 自定义raii类(如arrayguard)封装new[]与delete[],禁用拷贝操作以防止…

    2025年12月18日
    000
  • 如何自定义C++异常的错误信息 重载what()方法最佳实践

    在c++++中,自定义异常错误信息的推荐做法是继承std::exception并重载what()方法。1. 创建一个继承自std::exception的类,并添加用于存储错误信息的std::string成员变量;2. 在构造函数中接收错误信息字符串并初始化该成员变量;3. 重写what()方法,返回…

    2025年12月18日 好文分享
    000
  • 如何调试智能指针的内存问题 使用工具检测智能指针的内存泄漏

    是的,智能指针可能因循环引用、错误资源管理或与裸指针混用等原因导致内存泄漏。1. 循环引用:如std::shared_ptr相互持有,造成引用计数无法归零,对象无法析构;2. 自定义删除器错误:未正确释放资源或误删其他资源;3. 与裸指针混用:可能导致双重释放或内存损坏;4. 非内存资源管理不当:文…

    2025年12月18日 好文分享
    000
  • 如何用C++11范围for循环遍历容器 更简洁的迭代写法

    范围for循环是c++++11引入的语法结构,用于简化容器或数组的遍历。1. 它通过自动调用begin()和end()实现迭代,无需手动使用迭代器;2. 使用引用(如const int&)可避免拷贝提升性能;3. 不应在循环中修改容器结构以防止迭代器失效;4. 支持标准库容器、c风格数组及自…

    2025年12月18日 好文分享
    000
  • 怎样实现C++中的观察者模式 信号槽机制与现代事件系统设计

    观察者模式的实现可通过传统方法、信号槽机制或现代事件系统完成。1. 传统方法需手动管理观察者列表,包含主题、观察者、具体主题和具体观察者四个核心部分;2. 信号槽机制如qt的实现,通过connect连接信号与槽函数,自动处理通知流程,简化了观察者管理;3. 现代事件系统使用eventmanager和…

    2025年12月18日 好文分享
    000
  • C++模板元编程如何入门 编译期计算与类型操作基础

    学c++++模板元编程的核心是利用模板语法在编译阶段进行运算和类型处理,以生成高效代码。1. 从模板函数入手,通过递归实例化实现编译期常量计算,如阶乘计算;2. 使用type traits进行类型操作,判断、转换或选择类型,适配泛型代码行为;3. 用模板特化和递归模拟流程控制,替代if/else和循…

    2025年12月18日 好文分享
    000
  • 什么是C++的RAII机制 资源获取即初始化原则

    r#%#$#%@%@%$#%$#%#%#$%@_4921c++0e2d1f6005abe1f9ec2e2041909i是一种c++编程机制,通过对象生命周期自动管理资源。其核心原理是构造函数获取资源、析构函数释放资源,确保资源在异常或提前返回时也能正确释放。典型应用场景包括内存管理(如std::un…

    2025年12月18日 好文分享
    000
  • C++中枚举类型怎么用 enum和enum class使用场景

    enum和enum class的主要区别在于作用域和类型安全性。普通enum的枚举值暴露在外部作用域,易造成命名冲突,适合旧项目兼容或轻量级使用;而enum class具有作用域隔离、禁止隐式转换和显式指定底层类型等优势,适用于新项目和需要类型安全的场景。两者各有优劣,选择应基于项目需求和代码风格。…

    2025年12月18日 好文分享
    000
  • C++容器选择如何影响性能 不同场景下vector map unordered_map对比

    选择c++++容器需根据场景:频繁查找用unordered_map最快;小数据量或需顺序用vector;需要排序和范围查询则选map。①unordered_map基于哈希实现,平均查找o(1),适合快速查找、不关心顺序的场景,但存在哈希冲突风险;②vector在数据量小或需频繁遍历时性能更优,支持连…

    2025年12月18日 好文分享
    000
  • 怎样声明和使用常量 const与constexpr关键字解析

    const强调不变性,constexpr强调编译时可确定性,所有constexpr都是const,但反之不成立;const变量可在运行时初始化,而constexpr必须在编译时求值;选择const用于运行期不变值,选择constexpr用于需编译时常量的场景如数组大小、模板参数或编译时计算,以提升性…

    2025年12月18日
    000
  • 怎样编写C++的嵌套类 内部类与外部类的访问权限控制

    c++++中嵌套类的访问权限控制可通过友元机制实现。1. 嵌套类默认无法直接访问外部类成员,需将嵌套类声明为外部类的友元以访问其私有成员;2. 外部类若要访问嵌套类私有成员,则需在嵌套类中将外部类声明为友元;3. 嵌套类默认是私有的,外部无法直接访问,需显式指定public权限;4. 嵌套类不继承外…

    2025年12月18日 好文分享
    000
  • C++字符串处理如何避免低效 移动语义与SSO优化策略解析

    在c++++中提升字符串处理性能的关键在于合理利用移动语义和sso机制。1.sso通过将短字符串存储在栈上而非堆上来避免动态内存分配,多数编译器支持最多15~22个字符的优化长度;2.移动语义通过“偷取”资源的方式减少深拷贝开销,在返回局部变量时可依赖编译器rvo优化,而在变量转移所有权时应显式使用…

    2025年12月18日 好文分享
    000
  • 怎样减少C++函数调用开销 内联函数与模板元编程实践指南

    内联函数和模板元编程能有效减少c++++函数调用开销。1. 内联函数通过将函数体插入调用处,省去栈操作和跳转开销,适用于小型高频函数如访问器,但需避免代码膨胀;2. 模板元编程在编译期完成计算如阶乘,避免运行时损耗,适合常量表达式和静态逻辑,但增加编译时间和调试难度;3. 实际中两者常结合使用,如s…

    2025年12月18日 好文分享
    000
  • 什么是C++的placement new 固定地址对象构造技术

    c++++中的placement new允许在预分配内存上构造对象,其形式为new (pointer) type,用于性能优化、内存布局控制及自定义容器实现。使用时需注意手动调用析构函数、确保内存对齐、避免重复构造对象,并适用于内存池、序列化及嵌入式系统等场景。 C++中的placement new…

    2025年12月18日 好文分享
    000
  • 观察者模式怎样编写 事件通知机制实现

    观察者模式通过定义被观察者与观察者的依赖关系,实现状态变化时的自动通知机制,适用于事件驱动场景如ui更新、消息推送等,其核心是解耦与广播通知,需注意及时移除观察者以避免内存泄漏,最终确保系统灵活性与可维护性。 观察者模式是一种行为设计模式,用于在对象之间建立一对多的依赖关系,当一个对象的状态发生改变…

    2025年12月18日
    000
  • C++11的auto关键字有什么优势 自动类型推导的使用场景与限制

    auto关键字在c++++11中通过自动推导变量类型提升了代码的简洁性与可读性,尤其适用于复杂模板类型和泛型编程。1. 使用auto可避免冗长的类型声明,如用auto it = myvec.begin()代替std::vector::iterator it = myvec.begin(); 2. 在…

    2025年12月18日 好文分享
    000

发表回复

登录后才能评论
关注微信