在现代操作系统中,进程是资源分配的基本单位,而线程是 CPU 分配调度的基本单位。每 个进程的创建都会伴随一个线程的创建,称为主线程,运行中的线程可以在当前进程中创建 更多的线程,这些线程共同享有进程中的资源。

线程不可能独立于进程存在(没有资源就无法计算),所以它的生命周期不会逾越其所属进 程的生命周期。

内核级线程模型

用户线程和内核调度实体(KSE,Kernel Scheduling Entities)是一对一的关系,大部分 编程语言的线程库都是对 OS 线程进行了一层封装,线程库创建的线程和不同的 KSE 进行 静态关联,因此其创建、调度、销毁都是直接由 OS 来负责,需要从用户态切换到内核态, 开销较大。

用户级线程模型

用户线程与 KSE 是多对 1 关系(M:1),这种线程的创建、销毁以及多个线程之间的协调 等操作都是由用户自己实现的线程库来负责,对 OS 内核透明,一个进程中所有创建的线程 都与同一个 KSE 在运行时动态关联,大部分语言的“协程”实现都属于这种模型。

这种实现方式比内核级线程更轻量级,对系统资源的消耗更小,所以协程的创建和调度花费 的代价比较小。而最大的缺点就是,当某个协程阻塞了 KSE,那么其他协程都会被阻塞(进 程被挂起)。所以需要在语言层面把一些阻塞式的操作重新封装为非阻塞形式,当执行这些 阻塞操作时协程需要主动让出自己,并通知其他协程在 KSE 上运行(async/await),避免 OS 切换到其他 KSE 而将整个进程挂起。

两级线程模型

用户线程与 KSE 是多对多关系 (M:N),这种实现综合了前两种模型的优点,为一个进程创 建多个 KSE,并且线程可以与不同的 KSE 在运行时进行动态关联。当某个 KSE 由于工作线 程的阻塞操作被内核调度出 CPU 时, 当前与其关联的其余用户线程 可以重新与其他 KSE 建立关联。

Go 中的 GPM 模型就属于这种实现方式,Go 通过实现一个运行时的调度器来负责 Goroutine 与 KSE 的动态关联。

此模型也被称为混合型线程模型,即用户调度器实现用户线程在 KSE 上的“调度”,内核调 度器实现 KSE 在 CPU 上的调度。