今天咱们开个新坑,聊聊并发编程。
很多人一上来就觉得 “多线程 = 快”,恨不得把所有代码都写成多线程,结果线上一跑,不仅没快,反而更慢了,还多了一堆莫名其妙的问题。
今天咱们就用大白话讲一讲 “多线程到底什么时候用”“用多少线程才合适”,以及它背后的 “隐形杀手”—— 上下文切换。
一、多线程到底是干嘛的?
简单来说:
- 你是一个老板,手下有个工人(单线程),他干活很卖力,但中间经常要等材料(比如等网络请求、等硬盘 IO)。
- 等材料的时候,他就站那儿摸鱼,啥也不干,你付着工资,他却在发呆。
- 这时候,你再雇几个工人(多线程),第一个工人等材料的时候,第二个、第三个工人就可以接着干别的活,这样整体效率就上去了。
多线程的本质,就是利用 “等待时间”,让 CPU 别闲着。
它解决的核心问题是:CPU 浪费比例严重。
二、关键指标:CPU 浪费比例
什么是 “CPU 浪费比例”?
就是 “等待时间” 和 “干活时间” 的比值。
CPU浪费比例 = 等待时间 / 干活时间
- 如果一个任务,干活 2ms,等待 42ms,那浪费比例就是 42 / 2 = 21。
- 这意味着,CPU 有 21 倍的时间在摸鱼,这时候开多线程,收益巨大。
- 如果一个任务,干活 2s,等待 1s,浪费比例是 0.5,这时候开多线程,收益就很小,甚至可能因为线程切换,反而更慢。
记住:多线程只适合 “CPU 浪费比例> 1” 的场景。 不是 “等待时间长” 就适合,而是 “等待时间远大于干活时间” 才适合。
三、线程数量怎么算?
很多人会问:“我开多少线程合适?”
答案是:看摸鱼时间。
还是刚才的例子:
- 干活:2ms
- 摸鱼:42ms
- 总耗时:44ms
如果只开 1 个线程,那它 42ms 在摸鱼,2ms 在干活,效率极低。
如果开 22 个线程:
- 每个线程摸鱼 42ms,干活 2ms。
- 当第 1 个线程开始摸鱼时,第 2 个线程就可以开始干活。
- 这样,CPU 几乎全程都在干活,没有浪费。
线程数 ≈ (等待时间 / 干活时间) + 1
这个公式不是绝对的,但它给了我们一个非常重要的直觉:线程数是由 “等待时间” 决定的,不是越多越好。
四、反例:不是所有场景都适合多线程
咱们再看一个反例:处理图片。
- 任务:从网络下载一张图片(等待 1s),然后对图片进行压缩(干活 2s)。
- 浪费比例:1 / 2 = 0.5。
这时候,如果你开 10 个线程:
- 每个线程都要先等 1s,再干 2s。
- 线程之间频繁切换,光 “换岗” 就耗了不少时间。
- 最终总耗时,可能比单线程还要长。
这就像:
- 雇 10 个工人干 1 个活,大家轮流上,光协调、排队的时间,比干活时间还长。
所以,多线程滥用只会自讨苦吃。
五、多线程的 “副作用”:上下文切换
当我们开了多线程,让 CPU “忙起来” 的时候,一个新的问题出现了:上下文切换。
咱们还是用工人的例子来理解:
- 现在你有 22 个工人,但只有一个工作台(CPU 核心)。
- 大家轮流上,每个人干一会儿,就换下一个。
- 每次换人,都要把上一个工人的 “干活进度”(比如算到哪一步了、手里拿了什么工具)记下来,再把下一个工人的进度恢复。
- 这个 “记下来、再恢复” 的过程,就是上下文切换。
1. 上下文切换的“开销”有多大?
在现代 CPU 上,一次上下文切换的时间大约是不到 1 毫秒。
听起来不多,但如果每秒切换 1000 多次,累积起来就是:
- 1000 次 / 秒 × 1ms / 次 = 1 秒 / 秒
- 这意味着,CPU 一半的时间都在 “换岗”,而不是在 “干活”。
这就是多线程的 “隐形消耗”。
2. 上下文切换不是越少越好!
很多人一看到切换开销,就想:“那我少开点线程,切换不就少了吗?”
这又走进了另一个误区:
- 如果线程太少,CPU 又会回到 “摸鱼” 的状态,浪费大量时间。
- 如果线程太多,切换太频繁,CPU 又会把时间都花在 “换岗” 上。
所以,线程数量和上下文切换次数需要达到一个平衡,才能达到最优性能。
- 线程太少 → CPU 浪费比例增高,整体变慢。
- 线程太多 → 上下文切换频繁,整体变慢。
性能最佳的核心,是线程数量合适,而不是上下文切换次数最少。
六、如何减少不必要的上下文切换?
既然切换是必要的,但又不能太多,我们该怎么办?
核心思路是:减少线程的 “无效等待”。
七、总结:多线程的 “适用边界” 与 “平衡之道”
最后,咱们用几句话总结一下今天的核心内容:



