多任务并行的需求引出了进程的概念。进程内部并发的需求引出了线程。
沿用上一篇中介绍进程提到的例子,一个进程就像我们在厨房做菜的过程,操作系统会为进程分配资源,就像油盐糖醋。假如有一个进程是要做东坡肉和西红柿炒鸡蛋这两盘菜,如果只有进程的概念,方案一是先等东坡肉做完了再去做西红柿炒鸡蛋(串行),方案二是分配两个厨房和两套厨具调料,可以同时做两道菜(并行,但是分成了两个进程)。如果引入了线程的概念,就可以只用一套厨具调料,在等东坡肉慢慢煮的时候去做西红柿炒鸡蛋(实现了进程内部的并行)。
所以,线程就是为了实现共享进程资源的前提下,实现进程内部的并发执行。
本文中所描述的线程,只是广义上的概念,与实际操作系统实现并不一定相同。比如在 Linux 中,线程相当于是进程,用相同的进程描述符描述,且使用相同的调度算法。
Thread,线程是进程当中的一条执行流程,又被称作轻量级进程。在传统的操作系统中,每个进程都有一个地址空间和一个控制线程(主线程)。
同一个进程中,每个线程共享地址空间、全局变量、打开文件、子进程、即将发生的定时器、信号与信号处理程序、账户信息。每个线程都可以访问进程地址空间内每个内存地址,因此一个线程可以读取、写入甚至擦除另一个线程的堆栈。
每个线程具有各自的程序计数器、寄存器、堆栈、状态。
线程的优点:
线程的缺点:
线程表和内核中的进程表类似,不过它仅仅记录各个线程的属性,如每个线程的程序计数器、堆栈指针、寄存器和状态。
线程的基本状态和进程一样,但是不同用户线程可以自定义不同的状态。(比如 Java 中的六种状态:新创建、可运行、被阻塞、等待、计时等待、被终止)
进程的上下文切换还可能会导致 TLB 的更新,从而引发 cache miss,造成不必要的开销。
主要有三种线程实现的方式:用户线程、内核线程和轻量级进程。
在早期的操作系统中,所有的线程都是在用户空间下实现的,操作系统只能看到线程所属的进程,而不能看到线程。
在用户空间实现的线程,是由用户进程(通过调用用户级的线程库)来完成线程的管理, 内核对线程一无所知。在用户空间管理线程时,每个进程需要有其专用的线程表用来跟踪该进程中的线程。
操作系统只能看到线程所属的进程,而不能看到线程。CPU 调度也是直接调度进程,随后再由进程自定义的调度算法进行线程的调度。
优点:
缺点:
几乎所有的现代操作系统都支持内核线程。
内核线程是由操作系统管理的,线程表是放在内核中的,这样线程的管理都是由操作系统负责。
每个内核线程可以视为内核的一个分身。
内核线性没有用户地址空间的概念,使用的是所有进程共享的内核地址空间,但是调度的时候会借用前一个进程的地址空间。
采用一对一的线程模型。
内核线程的祖先线程是 kthreadd,pid 为 2。
优点:
缺点:
Light Weight Process,是内核支持的用户线程,是内核线程的高度抽象。一个进程可有一个或多个 LWP,每个 LWP 由一个单独的内核线程来支持,LWP 是由内核管理并像普通进程一样被调度。
LWP 与父进程共享地址空间和系统资源。
LWP 与普通进程的区别在于它只有一个最小的执行上下文和调度程序所需的统计信息,代表程序的执行线程。
LWP 的许多操作都要进行系统调用,切换用户态和内核态,因此效率不高。
每个 LWP 都会与一个内核线程相关联,以此来实现由内核支持的用户线程。这个关联现在是 NPTL(Next POSIX Thread Library)来做的,实现了对 POSIX 标准的兼容。
Linux 中,一个用户线程对应一个轻量级进程,而一个轻量级进程对应一个特定的内核线程。
在 LWP 之上也可以使用用户线程,LWP 与用户线程的对应关系有三种:一对一、一对多和多对多。
一个 LWP 对应一个用户线程。
一个 LWP 对应多个用户线程。
多个 LWP 对应多个用户线程。可以综合前两种模式的优势。
用户线程和内核线程之间的关联方式有三种,即一对一、多对一、多对多。
多个用户线程对应一个内核线程。内核线程相当于与整个进程相关联,CPU 调度内核线程,即相当于调度进程,然后进程再自定义地调度线程。
线程管理由用户空间的线程库处理,不需要切换内核态,效率高。
但是如果线程阻塞,那么整个进程也会被阻塞,且不能发挥多核 CPU 的优势。
一个用户线程对应一个内核线程,是在内核空间实现线程所采用的模型。调度内核线程,就相当于调度一个个用户线程。
解决了一对多模型的两个问题,但是开销更大。另外,由于每个用户线程都需要一个内核线程,所以可创建的线程数会受到限制。
Windows/Linux 使用的都是一对一的模型。
多个用户线程对应多个内核线程。能够综合上两种模型的优点,但是实现较为复杂。
为了使编写可移植线程程序成为可能,IEEE 在 IEEE 标准 1003.1c 中定义了线程标准。线程包被定义为 Pthreads
。大部分的 UNIX 系统支持它。这个标准定义了 60 多种功能调用。
POSIX 线程(通常称为 pthreads)是一种独立于语言而存在的执行模型,以及并行执行模型。它允许程序控制时间上重叠的多个不同的工作流程。每个工作流程都称为一个线程,可以通过调用 POSIX Threads API 来实现对这些流程的创建和控制。可以把它理解为线程的标准。
POSIX Threads 的实现在许多类似且符合 POSIX 的操作系统上可用,例如 FreeBSD、NetBSD、OpenBSD、Linux、macOS、Android、Solaris,它在现有 Windows API 之上实现了 pthread。
每一个 CPU 核心都会有一个 idle 线程,当系统没有调度 CPU 资源的时候,会进入 idle 线程空转 CPU,以达到省电的目的。
idle 线程实质上是内核线程,是被静态创建的。
线程相比进程能减少开销,体现在:
线程是为了实现共享进程资源的前提下,实现进程内部的并发执行。
线程共享进程的地址空间、数据、文件等信息,且拥有自己的寄存器和堆栈等信息。
同一个进程内的线程的上下文切换代价更小。
线程的实现方式通常有三种:用户线程、内核线程和轻量级进程。