假共享不是bug或数据竞争,但会导致多线程性能暴跌;核心解法是让高频写的变量分属不同cache line,常用alignas(64)对齐,辅以padding或std::hardware_destructive_interference_size兜底,并优先使用thread_local避免伪共享。

假共享(false sharing)不是 bug,也不是数据竞争,但会让多线程程序性能暴跌——有时单线程跑 100ms,4 线程反而要 400ms。核心解法就一条:让高频写的变量不挤在同一个 cache line 里。
为什么 alignas(64) 是最常用起点
现代 CPU 的 cache line 大多是 64 字节,alignas(64) 能确保变量或结构体起始地址对齐到 64 字节边界,从而大概率独占一行。但要注意:
-
alignas(64)只控制起始地址对齐,不保证整个对象不跨行;如果结构体本身超过 64 字节,仍可能和下一个对象共享缓存行 - 对齐后内存占用变大,数组场景下(比如
Counter arr[1024])总内存可能翻几倍,得权衡 - 某些老平台或嵌入式芯片
cache line是 32 字节,硬写 64 可能浪费,优先用std::hardware_destructive_interference_size
std::hardware_destructive_interference_size 怎么用才稳妥
这个常量是 C++17 引入的“理论上最安全的隔离尺寸”,但它在部分编译器(如旧版 GCC 或 MSVC)里可能未定义或返回 0。实际写法得加兜底:
#include <new>
#if defined(__cpp_lib_hardware_interference_size) && __cpp_lib_hardware_interference_size >= 201606L
constexpr auto cache_line = std::hardware_destructive_interference_size;
#else
constexpr auto cache_line = 64;
#endif
<p>alignas(cache_line) std::atomic<int64_t> counter;
</int64_t></p></new>
别直接用 std::hardware_destructive_interference_size,没宏保护容易编译失败。
结构体填充 padding 比单纯 alignas 更可控
对齐只管开头,填充能真正撑满整行。比如两个计数器要彻底隔离:
struct PaddedCounter {
alignas(64) std::atomic<int64_t> value;
char pad[64 - sizeof(std::atomic<int64_t>)]; // 精确填满一行
};
</int64_t></int64_t>
这种写法比 alignas(64) std::atomic<int64_t> a, b;</int64_t> 更可靠,因为后者只是让 a 和 b 都对齐到 64 字节边界,但若分配在相邻内存,仍可能落在同一行(取决于分配器行为)。
哪些变量真需要防伪共享,哪些不用
不是所有共享变量都要处理。重点盯住:
- 被多个线程高频写入的变量(如统计计数器、状态标志),读多写少的变量影响小
- 放在同一结构体/数组里的相邻字段,尤其是
std::vector<counter></counter>这种连续布局 - 用
std::atomic修饰但没隔离的变量——原子操作本身不解决伪共享 - 完全可以用
thread_local替代的场景(比如每个线程私有计数),优先 TLS,比对齐更干净
最容易被忽略的是:伪共享只在有写操作时才触发。两个线程只读同一个 cache line 里的不同变量,完全没事;一旦其中一方写,问题立刻出现。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











