协程 · 线程 · 进程 · OS线程 · 超线程 · 时间分片

你列的这 6 个其实是不同层次的东西,还混进了「机制」和「实体」。本页先用一张主图理顺层次,再逐个拆解 + 对比。

① 主层次图
② CPU 时间分片
③ CPU 超线程
④ 进程
⑤ OS 级别线程
⑥ 用户线程(绿线程)
⑦ 协程
⑧ 总对比 & 关系

① 先纠正:你清单里混了三层

你给的顺序(协程→线程→进程→OS线程→超线程→分片)把「硬件 / 操作系统 / 运行时」三层,以及一个「调度机制」搅在一起了。

真实的主从关系是自下而上的:硬件提供可并行的 CPU 通道 → 操作系统把进程/内核线程调度上去并做时间分片 → 运行时再把协程/用户线程 multiplex 到内核线程上。而「时间分片」是横向的调度机制,「超线程」是硬件扩出更多逻辑 CPU——它们不是和进程平级的「一层」。
运行时层 · 协程 / 用户线程(goroutine、asyncio…)
管理器:语言运行时 · 切换不进内核 · 最轻
↑ M : N 映射(运行时调度器把 N 个用户任务铺到 M 个内核线程)
操作系统层 · 进程 ⊃ 内核线程(LWP / OS线程)
管理器:OS 调度器 · 切换要进内核、可能换页表
↑ 1 : 1 绑定(每个内核线程占一个逻辑 CPU 的时间片)
硬件层 · 逻辑 CPU(物理核 × 超线程)+ 时间分片机制
提供并行通道 + 时间片轮转 · 真正干活的硅
点任意一层看它在哪一堆、由谁管、和上下层怎么绑。
运行时层(用户态,不进内核):协程与用户线程由语言运行时在用户空间管理。它们最轻量,切换只保存/恢复几个寄存器,开销纳秒级。一个进程里可以有成百上千个。它们最终要靠『绑定』映射到下面的 OS 线程才能上 CPU。

顺便厘清两个易混的词

  • 「线程」到底是啥? 广义上包含两类:用户级线程(运行时管,如 goroutine)和 内核级线程=你说的「操作系统级别线程」(OS 管,pthread)。很多人把它们混为一谈——goroutine 不是 OS 线程。
  • 协程 vs 用户线程: 协程是「协作式」的用户线程(必须自己 yield 才让出);而 goroutine 这类用户线程是「抢占式」由运行时抢的。所以协程 ⊂ 用户线程这个概念大家族,是最轻的一档。

② CPU 时间分片(Time Slicing)— 并发的「机制」

它不是一种「实体」,而是操作系统调度内核线程的手段:每个可运行线程分到一个时间片(quantum),用完(或被更高优先级抢占)就切到下一个。单核上这是「并发假象」,多核上是「并行 + 分片」叠加。

注意:时间分片作用在「内核线程 / 进程」这一层,协程和纯用户线程感受不到它——它们由运行时自己调度,只有真正跑到 OS 线程上、被 OS 调度时才会被切片。

单核上两个内核线程轮流占 CPU

点「下一个时间片」逐步揭示调度过程
时间片由定时器中断(tick)驱动:每次 tick,OS 调度器判断是否要抢占当前线程。切换线程 ≈ 保存旧寄存器、恢复新寄存器、可能切换页表——这就是「上下文切换」开销。分片越细,响应越流畅但切换开销占比越高。

为什么需要它

  • 公平性:不能让一个线程死占 CPU,其他饿死。
  • 响应性:IO 阻塞的线程让出,计算线程立刻顶上。
  • 多任务假象:单核也能「同时」跑很多程序,用户无感。

但分片本身不增加算力——它只是把已有的 CPU 时间切开分配。要真正同时干多件事,得靠多核 / 超线程(下一节)。

③ CPU 超线程(Hyper-Threading / SMT)

这是纯硬件特性。一个物理核心内部,复制出两套独立的「架构状态」(寄存器、程序计数器等),但共享执行单元、缓存、流水线。于是 OS 看到的是 2 个「逻辑 CPU」,可同时塞两个线程进去。

物理核心 (Physical Core)
逻辑 CPU 0
独立寄存器/PC
独立寄存器/PC
逻辑 CPU 1
⬇ 共享:执行单元(ALU) · 流水线 · L1/L2 缓存 · 访存通道
逻辑 CPU 0:一套独立寄存器 + PC。OS 把它当成一个完整处理器来调度内核线程。两个逻辑 CPU 共享下面的执行引擎与缓存。
关键点: 超线程是「一个核假装成两个核」,给 OS 多出一个可调度通道,让分片能真正并行地铺两条指令流。但它不加倍算力峰值——两个逻辑 CPU 抢同一套执行单元,通常只带来 ~30% 吞吐提升,而非翻倍。真正的翻倍要靠「多物理核」。

和「时间分片」的关系

两者常被一起提,但角色不同:

  • 超线程 = 横向「扩通道」:让 1 核能并行跑 2 条流(硬件提供并行度)。
  • 时间分片 = 纵向「切时间」:把每个通道的时间轮流分配给不同线程(OS 提供公平/响应)。

所以一台 8 核 16 线程(每核 2 超线程) 的机器,OS 看到 16 个逻辑 CPU,每个再用时间分片调度一堆内核线程——这就是你看到的「能跑远超 16 个进程却很流畅」的底层原因。

④ 进程(Process)— OS 的资源容器

进程是操作系统分配资源的基本单位:它拥有独立的虚拟地址空间(自己的页表/mm_struct)、文件描述符表、信号处理等。一个进程里至少包含 1 个内核线程(主线程),多线程进程则包含多个。

进程 PID=1001(独立地址空间)
📦 独立虚拟地址空间:text / data / heap / mmap / stack(其他进程看不到)
内核线程 T1
主线程
内核线程 T2
worker
内核线程 T3
worker
主线程(内核线程):进程创建时自带的第一条执行流,等价于 OS 线程。它有独立的内核栈+用户栈+寄存器,是 OS 调度单位。
进程开销在哪: 创建/切换进程要建/换整个页表(TLB 刷新)、拷贝资源,最重。同进程内线程切换则不用换页表(共享地址空间),所以线程比进程轻。代价是进程间天然隔离、互崩不影响;线程共享内存,一个段错误拖垮全组。

⑤ 操作系统级别线程(内核线程 / LWP)

这就是你清单里单独的「操作系统级别线程」,也是 pthread_create 造出来的东西。它的正式名字是内核线程 / 轻量级进程(LWP)——操作系统真正调度上 CPU 的单位

逻辑 CPU0
运行 T1
逻辑 CPU1
空闲
逻辑 CPU2
运行 T2
逻辑 CPU3
运行 T3
逻辑 CPU 0:此时正运行进程 A 的内核线程 T1。内核线程与逻辑 CPU 是 1:1 绑定占用关系。
内核线程的「身份证」: 它有独立的内核栈 + 用户栈 + 一套寄存器,是一个 task_struct(参见上一篇「Linux 进程/线程结构体」)。切换它要进内核、保存/恢复寄存器、若跨进程还要换页表。这就是它与下面「用户线程/协程」最本质的开销差别。

内核线程 vs 进程(回顾)

维度进程内核线程(OS线程)
地址空间独立(自己一套页表)共享所属进程的页表
角色资源容器执行流 / 调度单位
创建开销重(建页表、拷贝资源)较轻(共享地址空间)
切换换页表(TLB 刷)同进程内不换页表

⑥ 用户线程(绿线程 / Green Thread)— 运行时管理的线程

这就是广义「线程」里不属于 OS 的那一半goroutine、Java 虚拟线程、Erlang 进程、老版 Java 绿线程都是它。它们完全存在于用户态,OS 看不见,由语言运行时用调度器把成千上万个用户线程多路复用到少量内核线程上(M:N 模型)。

N 个用户线程(goroutine)—— 运行时在用户态调度,切换不进内核
G1
G2
G3
G4
G5
G6
↑ M : N 复用(Go 的 G-P-M 调度器把 G 铺到 P 再到内核线程 M)
M 个内核线程(OS 线程,1:1 对应逻辑 CPU)
内核线程 M1
内核线程 M2
用户线程 G1:一个 goroutine。运行时给它分配极小栈(2~8KB),切换只改几个寄存器,纳秒级。
为什么要有它: OS 线程虽轻于进程,但每开一个就要内核栈+默认 MB 级栈+进内核调度,开 10 万就扛不住。用户线程把栈压到 KB 级、调度留在用户态,于是能轻松开百万级并发。代价: 一个用户线程若跑死循环不 yield,会霸占其下的内核线程,同批 G 全卡住(所以 Go 才引入异步抢占)。

⑦ 协程(Coroutine)— 最轻的协作式用户任务

协程是用户线程里「协作式」的那一档:它不会被迫让出 CPU,必须自己主动 yield(或 await)才把控制权交还调度器。Python asyncio、Lua、Kotlin 协程、C++20 协程都是典型。

协程 A 运行中
干活…遇到 await / yield
↓ A 主动让出(yield / await)
事件循环(调度器)
挑下一个就绪协程
↓ 调度器切换到 B
协程 B 运行中
从暂停点恢复
↓ B 也 yield 了
回到 A 继续
A 从 yield 后恢复
协程 A 开始运行,执行到某个 IO 点或显式 yield/await。
协程的核心特征:协作式 + 单线程 + 无需锁。 因为同一时刻只有一个协程在跑、且切换点明确,天然没有数据竞争。它通常跑在一个用户线程(甚至一个内核线程)上,靠事件循环驱动——这就是 Node.js / asyncio 高并发低开销的来源。对比:内核线程是抢占式(OS 随时抢),所以才需要锁。

协程 vs 用户线程(goroutine)

  • 协程:协作式,必须 yield;通常单线程事件循环;切换点确定、无锁。
  • goroutine 类用户线程:抢占式(Go 1.14+ 异步抢占),可分布在多线程多核;需要运行时调度器,偶有竞态需通道/锁。

一句话:协程 ⊂ 用户线程。它们都是「用户态、不进内核切换」这一大类,区别在「让不让 OS/运行时强行抢」。而用户线程 ⊃ 协程,又隔着一层 M:N 才到 OS 线程。

⑧ 总对比表

维度协程用户线程(绿线程)OS线程(内核线程)进程超线程时间分片
所在层运行时运行时操作系统操作系统硬件机制(OS)
管理者运行时/事件循环语言运行时OS 调度器OSCPU 硬件OS 定时器
调度方式协作式多协作/抢占抢占式抢占式硬件并行时间轮转
切换是否进内核是(切线程时)
切换开销极小(寄存器)很小中(进内核+可能换页表)大(换页表)
是否共享地址空间同线程内同进程内同进程内独立核内共享
并行能力单线程靠底层多线程跨核真并行跨核真并行核内并行假象/叠加
典型代表asyncio/Lua/Kotlingoroutine/虚拟线程pthreadfork 出的程序Intel HTOS 调度

一张关系图收尾(从最轻到最重 / 从软件到硬件)

协程(协作式用户任务)
⊂ 用户线程大类
↑ 同类(用户态,不进内核切换)
用户线程 / 绿线程(goroutine…)
M : N 多路复用到 OS 线程
↑ M : N 复用(运行时调度器)
OS 线程 / 内核线程(LWP)
1 : 1 占用逻辑 CPU,抢占式
↑ 1 : 1 绑定 + 时间分片轮转
逻辑 CPU(物理核 × 超线程提供)
真正干活的硅,可并行
↑ 进程把所有上述「装」在一起
进程 = 地址空间 + 文件等资源 + ≥1 个内核线程
资源容器,最重
记忆口诀: 协程是「自己让」的用户任务;用户线程是「运行时管」的轻量执行流;OS 线程是「内核调度、能真并行」的单位;进程是「装这一切的资源盒子」;超线程是「硬件多给的并行通道」;时间分片是「OS 在这些通道上切时间」的机制。前四个是实体(软件层),后两个是硬件/机制——这正是你原来清单里需要拆开的关键。