CPU并发程序设计与性能优化 - 第1节

并发与并行:别搞混了,这两件事不一样

先问个问题:你手上有两个任务,是同时做还是轮流做?

很多人把「并发」和「并行」当同义词用。但在 CPU 优化领域,这两个词差了十万八千里。我当年刚接触多线程时也犯过这个错,调了半个月的性能瓶颈,结果发现根本没用上多核——说白了,我是并发,不是并行。

  • 并发(Concurrency):多个任务在同一时间段内交替执行。单核 CPU 也能做到,靠的是时间片轮转。
  • 并行(Parallelism):多个任务在同一时刻真正同时执行。必须有多核或多 CPU 支持。

一句话区分:并发是逻辑上的同时发生,并行是物理上的同时发生。

举个实际例子。我做过一个日志采集系统,单核 CPU 上跑 8 个线程,每秒轮询 100 个文件。看起来线程很多,但其实 CPU 一秒内切来切去,每次只干一件事。这就是并发。后来换了 8 核服务器,每个线程分配到独立核上,同时干活,这才是并行。

CPU 架构对并发的支持

现在的 CPU 为了榨干性能,从硬件层面就给了大量并发支持。我们来拆开看看。

硬件特性 作用 实际影响
多核 / 多 CPU 真正并行执行 多个线程分配到不同核心上,同时运行
超线程(Hyper-Threading) 一个物理核心模拟两个逻辑核心 提升指令级并行度,但不是真并行
SIMD(单指令多数据流) 一条指令处理多个数据 向量化运算加速,适合批量计算
乱序执行(Out-of-Order) 动态调整指令执行顺序 隐藏流水线停顿,提升吞吐
缓存层次(L1/L2/L3) 减少内存访问延迟 缓存命中率直接影响并发性能

这里我特别想说一下超线程。别被「逻辑核心」骗了。我曾经在压测时发现,把线程数设成 CPU 逻辑核心数(比如 16 线程跑在 8 核 16 逻辑核上),反而比设成物理核心数(8 线程)更慢。为什么会这样?因为超线程共享执行单元和缓存,如果两个逻辑核上的线程争抢资源,反而会互相拖累。嗯,这个坑我踩得比较深。

我的建议:初期调优时,线程数先取物理核心数,观察 CPU 利用率再往上加。别盲目按逻辑核心数来。

并发优化的目标

聊了这么多硬件,那我们应该往哪个方向使力?并发优化的核心目标可以归纳为三个:

  1. 减少等待:让 CPU 尽量不闲着。等待 I/O 时干点别的,用异步 / 非阻塞替代同步。
  2. 减少争用:多个线程抢同一把锁,效率直线下降。优化锁的粒度,甚至用无锁数据结构。
  3. 利用局部性:缓存不命中,CPU 要等几百个周期去主存拿数据。尽量让数据和代码挨得近。

你看,这三个目标其实是互相关联的。减少争用往往能顺便减少等待。利用局部性又能减少缓存 MISS,间接提升了并行效率。

注意:不要所有场景都开多线程。如果任务本身很轻,线程创建切换的开销可能比任务本身还大。我见过有人对几十微秒的运算也用线程池——结果不如单线程快。

本章知识体系

下面这张图把本章的核心逻辑串了一遍。从基本概念出发,到硬件支持,再到优化目标。记不住没关系,后面每一章都会对其中一个点深入展开。

CPU 并发处理知识体系(第1章) 并发 vs 并行 逻辑交错 vs 物理同时 多核 / 多 CPU 真正并行 超线程 逻辑核心共享 SIMD 向量化运算 乱序执行 隐藏流水线停顿 缓存层次结构 L1 / L2 / L3 减少等待 异步 / 非阻塞 减少争用 细粒度锁 / 无锁 利用局部性 缓存友好数据布局 核心原则 并发优化不是一味加线程,而是从硬件特性出发,平衡等待、争用和局部性

这张图我建议你存下来。后面每讲一个优化技巧,你都可以回来看看它落在图中哪个位置——这样就不会迷路了。

小结:现在你心里有数了

嗯,这一章我们把地基打好了。你知道了并发和并行的本质区别,也了解了 CPU 硬件给了哪些武器,还清楚了优化到底在优化什么。

我记得带团队做第一个高性能服务时,大家对着性能火焰图一脸懵。后来我拉着他们对照这张知识体系图,一个个排查——发现是锁的粒度太粗,CPU 在争用上浪费了大量时间。改完之后吞吐直接翻倍。

所以别急,后面每一章都是针对这张图里的一个具体节点深挖。你会学会怎么测量、怎么定位、怎么改。咱们一章一章来。