一次搞懂进程线程和协程的区别
进程、线程、协程这三个词天天挂嘴边,但真让你说清它们到底差在哪、各自归谁管、什么场景用哪个,很多人就卡壳了。这篇用大白话把三者捋顺:进程是资源的地盘,线程是干活的,协程是程序自己攒的轻量调度,顺带讲讲 CPU 密集和 IO 密集到底该怎么选。
进程、线程、协程,这三个词但凡写过并发代码的都绕不开。
它们各自是啥、归谁管、谁开销大、什么场景用哪个。
先给个一句话的印象:
- 进程:操作系统分配资源的基本单位,是“地盘”
- 线程:CPU 调度的基本单位,是地盘里真正“干活的人”
- 协程:比线程还轻,操作系统根本不知道它的存在,是程序自己攒的调度
下面一个一个说。
进程 是一块独立的地盘
进程是啥?简单说,正在运行的程序就是进程。
程序本身只是躺在磁盘上的一堆指令和数据,没有生命。
你双击打开 QQ,操作系统把它的代码、数据加载进内存,分配好地址空间、建好栈和堆,然后把 CPU 控制权交给它——这一刻,程序“活”了,变成了进程。
打开任务管理器,里面列的每一个正在跑的程序,就是一个个进程。
操作系统靠 PCB 管进程
操作系统怎么管这么多进程?靠一个叫 PCB(Process Control Block,进程控制块)的东西。
你可以把 PCB 理解成每个进程的“户口本”。
# PCB 里大致记着这些信息
- 进程标识号(这进程是谁)
- 进程状态(它现在在干嘛)
- 进程优先级(该不该优先照顾它)
- 寄存器内容(它上次执行到哪了)
- 文件系统指针(它开了哪些文件)
操作系统就是靠这本“户口本”来感知和控制每个进程的,PCB 是系统认识进程的唯一凭证。
进程的几种状态
一个进程这辈子,会在几种状态之间来回切换:
- 初始态:刚被创建,CPU 还被别人占着,只能先排队
- 就绪态:万事俱备,就等 CPU 翻牌子
- 执行态:正在用 CPU 干活,同一时刻只能有一个进程处于执行态(单核视角)
- 等待态:在等某件事完成(比如等磁盘读完数据),暂时交出 CPU
- 终止态:活干完了,退场
CPU 其实是轮流伺候的
这里有个关键认知。
不管单核还是多核,你看着好像一堆进程在同时跑,其实 CPU 是在飞快地轮流伺候它们。
CPU 把控制权从进程 A 切到进程 B,这个动作叫上下文切换。
切换时要先把 A 执行到哪了(寄存器、状态这些)存下来,再把 B 上次的现场恢复回去,B 就能从上次停的地方接着跑。
这个“存现场、换现场”的操作是有成本的,记住这点,后面对比开销时要用。
每个进程都以为自己独占内存
还有个很妙的设计叫虚拟存储器。
操作系统给每个进程都画了个大饼:让它以为整台机器的内存都归它一个人用。
实际上每个进程有自己独立的虚拟地址空间,CPU 通过地址翻译把虚拟地址转成真实的物理地址。
这么设计有三个好处:
- 内存不够用时,可以把不活跃的部分暂存到磁盘,用主存当高速缓存,省着用
- 每个进程地址空间长得一样,内存管理简单统一
- 进程之间互相隔离,A 碰不到 B 的地盘,一个进程崩了不会连累别人
但隔离也带来一个代价:进程之间默认没法直接共享数据,想通信得专门走进程间通信(IPC)那一套机制。
这就是进程最大的特点:安全、隔离,但也笨重。
线程 是地盘里干活的人
进程圈好了地盘、备好了资源,但光有地盘不干活啊。
真正执行代码的,是线程。
线程是程序执行流的最小单元,是 CPU 调度的基本单位。
一个进程里可以有好多个线程,它们共享这个进程的资源——同一块地址空间、同一批打开的文件。
打个比方:进程是一间公司(有办公室、有设备、有公共资料),线程就是公司里的员工,大家共用这些公共资源一起干活。
线程也有自己的小账本
虽然线程共享进程的大部分资源,但每个线程还是有点私货的。
操作系统给每个线程配一个 TCB(Thread Control Block,线程控制块),记着线程自己的东西:
- 线程标识符
- 一组寄存器(它执行到哪了)
- 运行状态、优先级
- 线程专有的存储区
- 信号屏蔽设置
所以线程有各自独立的调用栈,但共享进程的堆和全局数据。
线程的状态和进程一样,也是初始、就绪、执行、等待、终止那五种,切换时同样要做上下文切换。
进程和线程到底差在哪
这是面试最爱问的,我列几条最关键的:
- 进程是资源分配的单位;线程是 CPU 调度的单位
- 进程有完整独立的地址空间,互相隔离;同进程的多个线程共享地址空间
- 一个进程可以包含多个线程;线程很轻,基本不单独占系统资源
- 线程的创建、销毁、切换,开销都比进程小得多
- 线程共享地址空间很方便,但也带来麻烦:多个线程改同一个数据会打架,得加锁做同步互斥
- 一个线程崩了可能拖垮整个进程;而一个进程崩了不会影响别的进程
一句话总结这组取舍:
多进程:安全隔离,但切换开销大、效率低
多线程:切换开销小、效率高,但共享数据要操心同步,一崩全崩
协程 程序自己攒的轻量调度
前面进程和线程都是操作系统管的。
协程不一样,它操作系统根本不知道,完全是程序自己在玩。
协程又叫微线程,是比线程还轻量的存在。
它轻在哪
关键区别就一点:线程的切换要操作系统内核插手,协程的切换不用。
线程一旦阻塞(比如等 IO),是内核来挂起它、调度别的线程,这中间要陷入内核态,有成本。
协程呢,它只在一个线程里跑,是一段子程序主动让出、再切到另一段子程序,整个过程发生在用户态,压根不惊动操作系统。
你可以把协程理解成:一个线程内部,几段任务你一下我一下地轮流执行,而且是它们自己商量着来,不用找操作系统报备。
# 协程切换 vs 线程切换
线程切换:
线程A阻塞 -> 陷入内核 -> 内核调度 -> 切到线程B
(要进内核态,开销大)
协程切换:
协程A主动让出 -> 直接切到协程B
(全程用户态,开销极小)
协程的两个甜头
因为切换不走内核,协程带来两个实实在在的好处:
- 省开销:不用频繁创建、切换线程,省掉了内核调度那部分成本
- 不用加锁:协程在同一个线程里串着跑,同一时刻只有一个在动,天然不存在同时写一个变量的冲突,互斥锁、信号量这些统统省了
这就是为什么高并发场景大家爱用协程——它能用很小的代价,在一个线程里撑起成千上万个任务。
Go 的 goroutine 就是协程思路的典型代表,go 一个关键字就起一个,轻到你可以随手开几万个。
小结 到底怎么选
讲了半天,落到实处就一个问题:什么场景用哪个?
记住它们的本质差异就好选了:
- 进程:隔离最彻底,开销最大
- 线程:共享资源,开销中等,但要操心同步
- 协程:最轻,用户态调度,适合海量并发任务
对应到实际场景:
- CPU 密集型(比如大量计算、视频编码)-> 用多进程。活儿都压在 CPU 上,多进程能吃满多核,还互相隔离不打架
- IO 密集型(比如大量网络请求、读写文件)-> 用多线程。瓶颈在等 IO 不在算,线程切换开销小,等的时候正好让别的线程干活
- 又 CPU 密集又 IO 密集 -> 多进程 + 协程配合。多进程吃满多核算力,每个进程内再用协程扛住海量 IO 等待
说到底,这三个东西不是谁取代谁,而是不同粒度的工具。
进程给你隔离和安全,线程给你共享和效率,协程给你极致的轻量并发。
