很多人第一次写高并发代码时,会写出类似这样的代码:
std::atomic<long long> counter;
counter.fetch_add(1);
直觉是:
- 没加锁
- 原子操作
- 应该可以很好扩展
但实际运行结果往往是:
线程数增加 → 性能不升反降
CPU 很高 → 吞吐没提升
这类问题非常常见,而且经常被误解为:
“atomic 很慢”
但这个结论并不准确。
一、问题的本质
真正的问题不是 atomic,而是:
多个线程在频繁修改同一块 cache line
这会触发底层硬件机制:
- 缓存一致性协议
- cache line 在 CPU 核之间来回迁移
- 最终导致隐式串行化
一句话总结:
共享写热点(write contention)会让多核退化为单核执行
二、什么是 Cache Line(关键基础)
理解这个问题,必须先理解 cache line。
1. 定义
Cache Line 是 CPU 在缓存与内存之间传输数据的最小单位
在绝大多数现代 CPU 上:
Cache Line = 64 字节
2. 为什么存在
CPU 和内存的速度差距很大:
| L1 Cache | ~1 ns |
| L2 Cache | ~3–5 ns |
| L3 Cache | ~10–20 ns |
| 内存 | ~100 ns |
如果每次只读取 8 字节,效率很低。
因此 CPU 采用:
一次读取一整块(64B)
3. 实际访问行
long long x = arr[0];
你以为:
读了 8 字节
实际上:
读了 arr[0] 所在的整个 cache line(64 字节)
4. 关键结论
CPU 操作的不是变量,而是 cache line
三、cache line 如何导致性能问题
1. 多线程写同一个变量
std::atomic<long long> counter;
counter.fetch_add(1);
多个线程执行:
修改同一个 cache line
2. CPU 内部发生了什么
为了保证一致性,CPU 使用类似 MESI 的协议。
写操作必须:
获取 cache line 的独占权限(Modified)
多个核心同时写时:
Core A 修改 → Core B 使 A 失效 → 抢占
Core B 修改 → Core C 抢占
…
结果:
cache line 在多个核心之间反复迁移
3. 最终效果
同一时刻只能有一个核心修改数据
也就是说:
代码看起来是并发的,硬件层面却是串行的
四、一个直观实验
1. atomic 版本
std::atomic<long long> counter{0};
void work() {
for (int i = 0; i < 1e8; ++i) {
counter.fetch_add(1, std::memory_order_relaxed);
}
}
2. thread-local 版本
thread_local long long local = 0;
void work() {
for (int i = 0; i < 1e8; ++i) {
local++;
}
}
3. 典型结果
| 1 | 快 | 快 |
| 4 | 一般 | 接近线性提升 |
| 8 | 变慢 | 继续提升 |
4. 结论
问题不在 atomic,而在“多个线程写同一个 cache line”
五、这是一整类问题(不是个例)
这类问题在工程中非常常见,可以统一抽象为:
多线程 + 高频写 + 同一 cache line
下面是常见场景。
1. 全局计数器
global_qps++;
global_bytes += len;
2. 自旋锁
while (lock.test_and_set()) {}
所有线程不断访问同一个变量。
3. 无锁队列
tail.fetch_add(1);
head/tail 成为热点。
4. shared_ptr 引用计数
refcnt.fetch_add(1);
热点对象会放大问题。
5. 日志系统
write_pos.fetch_add(…)
高频写入导致竞争。
6. 内存分配器
早期:
全局 free list
后来:
thread cache(tcmalloc / jemalloc)
7. False Sharing(最隐蔽
struct {
long long a;
long long b;
};
两个线程
a++;
b++;
问题:
a 和 b 在同一个 cache line
结果仍然冲突。
六、如何识别这类问题
1. 扩展性异常
线程增加 → 性能不升反降
2. CPU 利用率异常
CPU 很高,但吞吐没增长
3. perf 工具
perf c2c record ./app
perf c2c report
可以看到:
- cache line bouncing
- HITM(hit modified)
七、解决方案(核心原则)
避免共享写,改为分散写 + 延迟汇总
1. thread-local(最推荐)
thread_local long long local;
2. 分片(sharding)
counter[thread_id % N]++;
3. padding / 对齐
struct alignas(64) Counter {
long long val;
};
4. 批量更新
local++;
if (local > 1024) {
global += local;
local = 0;
}
5. 分层聚合
线程 → worker → 请求(thread)→ 全局
八、几个重要认知纠正
1. atomic 不等于慢
正确说法:
atomic + 共享写 = 慢
2. 变量小不代表安全
bool flag;
仍然占用一个 cache line。
3. 无锁不等于高性能
无锁 ≠ 无竞争
九、总结
多核系统中,性能瓶颈往往不是计算,而是“多个核心争抢同一块 cache line”;一旦发生共享写热点,系统会被硬件强制串行化。
高性能并发设计的核心,不是“避免加锁”,而是“避免共享写”。

