常见的线程模型
线程之间的调度永远是一个复杂的话题,但是并发编程必然会涉及到操作系统对线程的调度。根据资源访问权限的不同,操作系统会把内存分为内核空间和用户空间,内核空间的指令代码具备直接调度计算机底层资源的能力,比如说I/O资源等;用户空间的代码没有访问计算底层资源的能力,需要通过系统调用等方式切换为内核态来实现对计算机底层资源的申请和调度。
线程作为操作系统能够调度的最小单位,也分为用户线程和内核线程,
- 用户线程由用户空间的代码创建、管理和销毁,线程的调度由用户空间的线程库完成(可能是编程语言层次的线程库),无需切换内核态,资源消耗少且高效。对
CPU的竞争是以所属进程的维度参与的,同一进程下的所有用户级线程只能分时复用进程被分配的CPU时间片,所以无法很好利用CPU多核运算的优势。我们一般情况下说的线程其实是指用户线程;- 内核线程由操作系统管理和调度,能够直接操作计算机底层的资源,线程切换的时候
CPU需要切换到内核态。它能够很好利用多核CPU并行计算的优势,开发人员可以通过系统调用使用内核线程。- 用户线程是无法被操作系统感知的,用户线程所属的进程或者内核线程才能被操作系统直接调度,分配
CPU的使用时间。对此衍生出了不同的线程模型,它们之间对CPU资源的使用程度各有千秋。
用户级线程模型
用户级线程模型中基本是一个进程对应一个内核线程,如下图所示,
进程内的多线程管理由用户代码完成,这使得线程的创建、切换和同步等工作显得异常轻量级和高效,但是这些复杂的逻辑需要在用户代码中实现,一般依赖于编程语言层次。同时进程内的多线程无法很好利用CPU多核进程的优势,只能通过分时复用的方式轮换执行。当进程内的任意进程阻塞,比如线程A请求I/O操作被阻塞,很可能导致整个进程范围内的阻塞,因为此时进程对应内核线程因为线程A的I/O阻塞而被剥夺CPU执行时间,导致整个进程失去了在CPU执行代码的权利。
内核级线程模型
内核级线程模型中,进程中的每个线程都会对应一个内核线程,如下图所示,
进程内每创建一个新的线程都会调用操作系统的线程库在内核创建一个新的内核线程与对应,线程的管理和调度有操作系统负责,这将导致每次线程切换上下文时都会从用户态切换到内核态,会有不小的资源消耗,同时创建线程的数量也会受制于操作系统内核创建可创建的内核线程数量。好处是多线程能够充分利用CPU的多核并行计算能力,因为每个线程可以独立被操作系统调度分配到CPU上执行指令,同时某个线程的阻塞并不会影响到进程内其他线程工作的执行。
两级线程模型
两级线程模型相当于用户级线程模式和内核级线程模型的结合,一个进程将会对应多个内核线程,由进程内的调度器决定进程内的线程如何与申请的内核线程对应,如下图所示,
进程会预先申请一定数量的内核线程,然后将自身创建的线程与内核进程进行对应。线程的调用和管理由进程内的调度器进行,而内核线程的调度和管理由操作系统负责。这种线程模型即能够有效降低线程创建和管理的资源消耗,也能够很好提供线程并行计算的能力。两级线程模型也给开发人员带来较大的技术挑战,因为开发人员需要在程序代码中模拟线程调度的细节,包括:线程切换时上下文信息的保存和恢复,栈空间大小的管理等。
–摘自《Go语言——高并发与微服务实战》


