写代码时总看到「并发」和「并行」两个词,文档里混着用,翻译过来又差不多,于是很多人默认它们是一回事。结果一到设并发数、算线程池、看接口限流的时候就开始犯迷糊。这篇不讲术语定义,就用排队打饭、食堂窗口这类场景,把两者的差别、关系和各自适用的情况说明白,最后给一套自己动手验证的小步骤。
写代码时总看到「并发」和「并行」两个词,文档里混着用,翻译过来又差不多,于是很多人默认它们是一回事。结果一到设并发数、算线程池、看接口限流的时候就开始犯迷糊:到底该加机器,还是该加请求数?这两个词背后对应的是完全不同的判断方向。
这篇不讲术语定义,就用排队打饭、食堂窗口这类场景,把两者的差别、关系和各自适用的情况说明白。读完之后你至少能做到一件事:看到一段代码或一个接口描述,能判断它说的是「任务怎么排」还是「任务怎么跑」,然后决定该调哪个旋钮。
为什么这两个词老是被搞混
最容易踩的坑是中文翻译。concurrency 和 parallelism 翻过来都带「同时」两个字,字面上完全看不出差别,但英文原词的语义侧重其实不一样:一个讲任务调度的交错,一个讲同一时刻真的一起跑。很多教程和产品文档自己都没分清,读者自然跟着糊。
想把混淆解开,不用先背定义,先把「谁在同时」拆成两个独立的问题:第一,任务之间有没有交错执行的痕迹;第二,执行单元有没有多个。这两个问题各有答案,四个组合一摆出来,概念自己就分开了。
- 任务交错
- 同一段时间内多个任务都在推进,但推进的瞬间是错开的
- 执行单元
- 真正干活的载体,比如 CPU 核、线程、进程、机器
- 调度
- 决定哪个任务此刻能拿到执行单元的那套规则
- 中文翻译太像:都是「同时做几件事」,字面上看不出差别
- 英文原词一个讲任务调度交错,一个讲同一时刻真的一起跑
- 很多文档和教程混用,导致读者以为是一回事
- 先把「谁在同时」拆成两个问题,混淆就散了
并发:一个人同时应付好几件事
想象你一个人坐在工位上,三个电话同时打进来。你不可能同时说三句话,只能接起一个、说两句、让对方稍等,再切到第二个。十分钟下来三件事都推进了一点,但任意一个瞬间你只在跟一个人讲话。这就是并发的核心画面:任务之间有交错,执行的人只有一个。
落到代码上,单核 CPU 上跑多任务、单线程事件循环、协程切换,都是这个路子。操作系统或运行时按时间片轮转,做出「大家都在动」的错觉。判断标准很简单:把执行过程打上时间戳,能看到任务 A、任务 B、任务 A 这样交替出现的痕迹。
| 观察点 | 并发场景下的表现 |
|---|---|
| 执行单元数量 | 通常只有一个在真正干活 |
| 任务推进方式 | 你一段我一段,交替推进 |
| 速度提升来源 | 把等待的时间利用起来,不是靠人多 |
| 典型代码形态 | 事件循环、协程、async 任务调度 |
什么情况下这个理解不适用:多核机器上你照样可以只写并发逻辑,只是底层运行时可能顺手把任务分到不同核上,看起来像并行了。所以「并发」描述的是你写代码时怎么组织任务,不是硬件层面到底几个核在转。
并行:几个人各干一件事,真的一起干
换一个场景:食堂中午排队,只开一个窗口,队伍排到门口;后来开了三个窗口,三个人同时打饭,队伍很快消化掉。这里的关键不是「切换得快」,而是同一时刻真的有三个人在分别干活。这才是并行。
代码里的并行,靠的是增加执行单元:多核 CPU 上开多进程、GPU 上多卡分摊、多台机器一起算。判据也很直接:同一时刻有多个执行单元在跑,速度提升来自「人多」。但前提是活能拆开——如果任务之间有强依赖,硬拆反而更慢,因为同步和通信的开销会吃掉收益。
- 先确认任务能不能拆把任务画成依赖图,看有没有可以独立跑的片段。全是串行依赖的链条,加人也没用。
- 再确认执行单元够不够看机器有几个核、能不能多开进程。单核上谈并行,基本是空谈。
- 最后量收益加执行单元前后各测一次总耗时,如果提升远小于投入,说明瓶颈不在算力,而在别处。
两者的关系:并行是并发的一种结果
这两个词不是并列关系,而是层次不一样。并发讲的是「任务怎么组织、怎么被调度」,并行讲的是「任务怎么被执行」。你可以只用并发调度,却不真正并行;但反过来,几乎不存在完全不并发的并行,因为总得有个东西负责把任务分派下去。
| 组合情况 | 是否可能 | 说明 |
|---|---|---|
| 并发但不并行 | 常见 | 单核跑多任务,靠切换推进 |
| 既并发又并行 | 常见 | 多核上既调度又真正同时执行 |
| 并行但不并发 | 几乎不存在 | 分派任务本身就需要调度行为 |
| 既不并发也不并行 | 常见 | 老老实实串行执行 |
还有一种容易忽略的情况:你在本机写的并发代码,部署到多核机器上跑,运行时可能自动把任务摊到多个核,表现上就带上了并行的效果。所以判断的时候要看两层:你写的逻辑是什么结构,底层实际拿到了几个执行单元。
代码里怎么一眼看出来是哪一种
看代码不用逐行读,抓两个特征就够:任务是靠「让出执行权」推进的,还是靠「增加执行单元」推进的。前者的关键词是事件循环、协程、async;后者的关键词是线程池、多进程、多卡、多机分摊。
更实用的一个判断角度是看瓶颈在「等」还是在「算」。大量等网络、等磁盘的场景,并发收益最大,因为你把原本干等的时间填满了;纯计算、CPU 一直跑满的场景,才需要并行来真正提高吞吐。搞反了就会出现「加了一堆并发,结果更慢」的情况。
- 看关键词:事件循环、协程、async 偏向并发;线程池、多进程、多卡、多机偏向并行
- 看瓶颈:等 IO 为主去想并发,算力打满去想并行
- 看失败现象:加并发数没变快,多半瓶颈在 CPU;加机器没变快,多半瓶颈在依赖或锁
- 拿不准就先量一把基线,别凭感觉拍数字
如果手上有接口,可以先测出延迟和耗时基线,再决定往哪个方向调。站内 /tools/speed-test 可以先跑一遍,拿到你自己的数字再对照着看,比看别人的结论靠谱。
搞清区别对实际开发有什么用
最直接的一个影响是设并发数。并发数是「同时挂多少个请求在服务端」,不是「用几个核」。很多人按本机核数去拍这个值,结果要么压垮下游,要么浪费了等待时间。这是两个完全不同的旋钮,转错了方向自然没效果。
第二个影响是本地和线上的差异。本地跑得快不代表线上快:单机并行受核数限制,接口侧还受服务端限流限制。你在本地压出来的数字,到线上可能完全对不上,因为限制条件换了。
| 你面对的场景 | 该往哪个方向想 | 常见误区 |
|---|---|---|
| 接口调用慢,但 CPU 很闲 | 并发方向:让等待时间重叠起来 | 直接去加机器 |
| 纯计算任务,CPU 长期打满 | 并行方向:增加真正的执行单元 | 疯狂加大并发数 |
| 加了并发反而更慢 | 瓶颈在锁、依赖或下游限流 | 以为是并发数还不够 |
| 加了机器没变化 | 任务本身拆不开或强依赖 | 以为是机器不够多 |
如果对术语边界还没把握,可以先到站内 /ai-dict/category/tech 把相关词条对齐一遍,再去看具体场景,会顺很多。涉及到各家模型的并发上限和限流规则,数值会变,别记死数,到站内价格与模型资料相关页面核对当时的数字。
动手验证:三步分清你手上的场景
概念讲完了,关键是你能不能对自己手上的活儿做出判断。下面这套三步走,不依赖任何工具,拿纸笔或者开个文档就能做完,做完之后你对当前场景的定位会清楚很多。
- 第一步,写下任务类型把任务归成三类:等网络、等磁盘、纯计算。分类依据是这段时间里执行单元到底在忙还是在闲。三类分完,方向基本就定了。
- 第二步,观察执行痕迹给任务打上开始和结束时间戳,看是交替推进(A 一段 B 一段 A 又一段),还是同一时刻多路一起推进。前者偏并发,后者偏并行。
- 第三步,改一个变量看结果只动一个变量:加大并发数,看总耗时变不变;或者加机器核数,看总耗时变不变。哪个有效,就说明瓶颈在哪一边。一次只动一个,不然分不清是谁的功劳。
验证过程中建议把结论随手记下来,记清楚任务类型、瓶颈位置、动哪个变量有效。下次遇到排期和容量估算,直接翻记录复用,比重新推一遍省事。要对比不同服务侧的限制条件,可以到站内中转站与价格追踪相关页面看当时的数据。
并发是让等待时间被填满,并行是让计算时间被摊薄。想清楚你在省哪种时间,旋钮就不会转错。下一步如果你正准备给接口调并发数,先去跑一遍延迟基线,再回来对照上面那张表判断瓶颈在哪边。→
常见问题
并发是不是一定比串行快?
不一定。如果任务是纯计算且只有一个执行单元,并发只是来回切换,还多了切换开销,可能更慢。并发真正能提速的场景是任务大量在等外部资源,比如等接口返回、等磁盘读写,这时候让别的任务先跑才有意义。判断前先看瓶颈在哪。
单核 CPU 上能有并行吗?
严格意义上同一时刻只有一个执行单元在算,做不到真正的并行。但单核上完全可以写出并发的程序,靠快速切换让多个任务看起来都在推进。如果你需要的是实打实的同一时刻多路执行,就得看有没有多个核或多个执行单元。
多线程就等于并行吗?
不等于。线程只是一种组织方式,多个线程是不是真的同时跑,取决于底层有没有多个可用的执行单元、以及运行时怎么调度。语言和框架里的线程模型差别很大,用之前建议先确认它的调度方式,别默认写了线程就是并行。
为什么加了并发数反而更慢、还报错?
常见原因是把并发当成了免费的加速手段。任务之间争抢同一份资源、或者对端有速率限制时,并发数越高冲突越多。这时候应该先降并发、加退避,再逐步抬高找拐点。限流相关的排查思路在站内已有专门文章,这里不重复。
这个概念跟调 API 有什么关系?
关系很直接。你设的并发数决定同一时刻挂多少个请求出去,直接影响会不会被限流、以及账单涨多快。具体单价和额度折算按各家官方口径,去站内 /official-api 看当时的数字,别拿旧截图当依据。