Linux进程/线程的相关概念梳理
建立 Linux 进程、线程模型、轻量级进程、内核与用户线程、协程和纤程的概念脉络。
Linux 的线程模型
Linux 诞生以来出现过三种线程模型:
- LinuxThreads:最早的模型,只实现了部分 POSIX Threads 标准。
- NGPT:Next Generation POSIX Threads,现已终止。
- NPTL:Native POSIX Thread Library,从 2.6 内核开始沿用至今的模型。
在 NPTL 中,Linux 使用用户线程与内核线程一一对应的 1:1 模型。Windows、Solaris、NetBSD、FreeBSD、macOS 和 iOS 等许多系统也采用这种模型。与 N:1 和 M:N 相比,1:1 模型的实现更简单。
通过 pthread 库创建线程时,pthread_create 会调用 clone 系统调用,最终创建一个 LWP(轻量级进程)和一个内核线程。在 NPTL 的 1:1 模型中,一个轻量级进程对应一个内核线程。该 LWP 在内核态拥有对应的 task_struct(进程描述符),是一个独立的线程调度单元。
Linux 的进程与线程
对于系统而言,进程和线程并没有严格区分。创建进程与创建线程都使用 fork 系统调用,只是传参不同。例如,子进程拥有独立的地址空间,而线程共享地址空间。内核调度的单位是 task。
可以认为,Linux 中的线程本质上是一组共享资源的进程。
LWP、用户线程与内核线程
LWP(lightweight process,轻量级进程)本质上是一种由内核支持的用户线程。
内核线程只访问内核空间,并共享相同的内核页表。所有内核线程的内核空间相同,而不同进程中的用户线程拥有各自的地址空间。
用户线程在标准定义中完全建立在用户空间。它的创建、同步、销毁与调度都在用户空间完成,不需要内核协助。这个定义与 LWP 存在一定交叉。
上下文切换
一个被抢占的 LWP 需要在用户态保存当前寄存器,切换到内核态后再保存对应内核线程的寄存器。恢复调度时需要分别恢复两个现场,至少涉及两次栈切换,并会造成一定程度的缓存失效。
Green threads
绿色线程是由运行时库或虚拟机调度的“线程”,完全在用户空间中管理。
JDK 1.2 之前,Java 在 Solaris 等平台上实现的线程模型属于绿色线程。早期的 Linux 线程模型也属于这一类型。常见的类似实现主要是协程,例如 Java 的 Quasar 和 Python 的 Greenlet。
Coroutine
协程是为非抢占式多任务处理而泛化的子例程,本质上是一种程序控制机制。协程可以有多个入口点,并在指定位置挂起和恢复执行。
协程分为两类:
- 非对称协程:协程之间存在调用链,通过
resume和yield两种操作传递程序控制权。一个非对称协程可以视为从属于它的调用者。 - 对称协程:只通过一种操作传递程序控制权,例如
coroutine.transfer。
Fiber
Fiber(纤程)基本可以认为是带有调度器的有栈协程实现。
Java 创建的线程是什么线程
Java 1.2 以前的部分实现使用 Green threads。1.2 以后,以 Linux 平台为例,线程创建最终调用 pthread_create,属于内核级线程。细节参见这里。