图 1:本文封面,进程各守一块地址空间,线程在进程内部共享堆、各持一叠栈。
先说明属性:这是一篇经典题解析,题目出自本地历史面试资料,与任何公司的当前招聘无关。这道题其实是两问:概念上进程和线程差在哪;工程上多线程什么时候真的省时间。第一问多数人会背,第二问才是拉开差距的地方。
一句话分野:进程是资源分配的单位,线程是 CPU 调度的单位。
程序跑起来,操作系统给它划一块独立的地址空间——代码段、数据段、堆、打开的文件,这份「家产」挂在进程名下。线程不领家产,它领的是一份「执行现场」:一组寄存器、程序计数器、一叠私有的栈。一个进程可以养多个线程,它们共享进程的全部家产,各跑各的执行现场。
Linux 的 pthreads 手册把这层关系写得很白:同一进程内的线程共享全局内存(数据段与堆)、进程 ID、打开的文件描述符、信号处理方式;而每个线程私有的是栈、线程 ID、errno 变量与信号掩码。共享与私有的清单,就是这道题的骨架。
五个维度把区别钉死。
地址空间:进程之间相互独立,A 写不坏 B 的内存;线程共享所属进程的堆与全局区,方便,但任何线程都能改写共享数据。创建与切换:进程要建地址空间、换页表,开销大;线程只换栈与寄存器现场,开销小得多。通信:进程走 IPC(管道、消息队列、共享内存);线程直接读写共享变量——但必须同步,否则就是数据竞争。崩溃影响:一个进程崩溃通常不拖累别人;一个线程段错误,整个进程一起死。角色定位:进程管资源,线程管执行。
第二问之 CPU 密集:核数就是天花板。
多线程省时间的前提,是有可并行的事、且有闲置的核。笔者在本机(10 个逻辑核)用 Node.js 的 worker_threads(真并行线程)实测:每个线程固定做 2 亿次开方运算,1 个线程 119ms,8 个线程 165ms——墙钟几乎不涨,因为核还够分,总吞吐翻了约 8 倍;加到 16 个线程,墙钟立刻涨到 298ms,超过核数后线程开始排队,再多线程也变不出 CPU。结论:CPU 密集任务的加速比上限就是核数,线程开多了只剩切换开销。
这里有个面试常追问的点:CPython 有 GIL(全局解释器锁),同一时刻只允许一个线程执行 Python 字节码,所以 Python 里 CPU 密集的多线程不但不提速还可能更慢,正确姿势是多进程;IO 密集则不受影响,因为线程等待时 GIL 会释放。
第二问之 IO 密集:等待时间可以被重叠。
另一类任务的瓶颈不在算,而在等:等网络、等磁盘、等下游接口。一个线程等待时 CPU 闲着,另一个线程正好顶上。同样实测:8 个任务各含 10ms 计算加 90ms 模拟 IO 等待,串行跑完 805ms,8 路并发只要 169ms——灰色的等待段被别的线程的计算填满了。边界同样存在:线程数超过「核数加 IO 并发上限」后收益递减,还要付切换与每线程一叠栈的内存成本。
实测代码。
const { Worker, isMainThread, workerData, parentPort } = require('node:worker_threads');
const os = require('node:os');
function work(n) { let x = 0; for (let i = 1; i <= n; i++) x += Math.sqrt(i); return x; }
if (!isMainThread) { parentPort.postMessage(work(workerData.n)); }
else (async () => {
const spawn = t => Promise.all(Array.from({ length: t }, () =>
new Promise(r => { const w = new Worker(__filename, { workerData: { n: 2e8 } }); w.on('message', r); })));
console.log('逻辑核数:', os.cpus().length);
for (const t of [1, 8, 16]) {
const t0 = Date.now(); await spawn(t);
console.log(`CPU 密集: ${t} 个线程各算 2 亿次 sqrt → 墙钟 ${Date.now() - t0}ms`);
}
const io = async (par) => { // 8 个任务,各 10ms 计算 + 90ms 等待
const t0 = Date.now(); let i = 0;
const busy = ms => { const e = Date.now() + ms; while (Date.now() < e); };
const runner = async () => { while (i < 8) { i++; busy(10); await new Promise(r => setTimeout(r, 90)); } };
await Promise.all(Array.from({ length: par }, runner));
return Date.now() - t0;
};
console.log(`IO 密集: 串行 ${await io(1)}ms → 8 路并发 ${await io(8)}ms`);
})();
图 2:五维对比表与两类场景的收益边界。
回到面试现场,建议的答题结构:先给一句话分野(资源分配 vs CPU 调度),再用「共享什么、私有什么」展开五个维度,然后主动把第二问拆成两个场景——CPU 密集看核数天花板(可顺势提 GIL 与多进程对策),IO 密集看等待重叠(收益递减有边界)。能说出边界条件,背诵题就答成了工程题。最后补一句选型经验:要隔离与健壮性选多进程,要低开销共享数据选多线程。你的项目用的是多进程还是多线程架构,当时怎么权衡的?评论区聊聊。