目录

  1. 概述
  2. 操作系统的三种线程模型
    1. 内核线程模型
    2. 用户线程模型
    3. 混合线程模型
  3. 操作系统的线程调度方式
    1. 协同式调度
    2. 独占式调度
  4. 线程优先级
  5. 附录

概述

❓操作系统为什么使用线程而不使用进程作为调度基本单位?

  • 线程是比进程更轻量级的调度执行单位
  • 线程的可以把一个进程的资源分配执行调度分开
  • 各个线程既可以共享进程资源(内存地址、文件 I/O 等),又可以独立调度(线程是 CPU 调度的基本单位)

操作系统的三种线程模型

📖操作系统中关于线程模型概念的回顾

  • 内核线程 KLT(1:1 模型):内核级线程(Kemel-Level Threads, KLT 也有叫做内核支持的线程),直接由操作系统内核支持,线程创建、销毁、切换开销较大
  • 用户线程 UT(1:N 模型):用户线程(User Thread,UT),建立在用户空间,系统内核不能感知用户线程的存在,线程创建、销毁、切换开销小
  • 轻量级进程 LWP(N:M 模型):(LWP,Light weight process)用户级线程和内核级线程之间的中间层,是由操作系统提供给用户的操作内核线程的接口的实现

内核线程模型

内核线程模型即完全依赖操作系统内核提供的内核线程(Kernel-Level Thread ,KLT)来实现多线程,在此模型下,线程的切换调度由系统内核完成,系统内核负责将多个线程执行的任务映射到各个 CPU 中去执行

KLT

📓内核线程模型下,线程调度以及线程切换都交给了 OS 来完成,非常耗时

用户线程模型

用户线程(User Thread)的优势在于不需要系统内核支援,劣势也在于没有系统内核的支援,所有的线程操作都需要用户程序自己处理。线程的创建、切换和调度都是需要考虑的问题,而且由于操作系统只把处理器资源分配到进程,那诸如“阻塞如何处理”、“多处理器系统中如何将线程映射到其他处理器上”这类问题解决起来将会异常困难,甚至对于经验不足的开发人员不可能完成

从广义上来讲,一个线程只要不是内核线程,就可以认为是用户线程,从此定义上来讲,轻量级进程也属于用户线程,但轻量级进程的实现是建立在内核之上的,许多操作都要进行系统调用,效率会受到限制

✨使用用户线程实现的程序一般都比较复杂,此处所讲的“复杂”,并不表示程序中必须编写了复杂的代码实现需求,使用用户线程的程序,很多都依赖特定的线程库来完成基本的线程操作,复杂性都封装在线程库之中;除了以前不支持多线程操作系统中的程序与少数有特殊需求的程序外,现在使用用户线程的程序越来越少了,Java、Ruby 等语言都曾经使用过用户线程,最终又都放弃使用它

📓用户线程模型下,线程调度以及线程切换都交给了开发者来完成,加重了开发人员的工作负担

混合线程模型

混合线程模型将内核线程与用户线程一起混合使用,是一种折中的体现,在这种混合实现下,既存在用户线程,也存在轻量级进程

✨用户线程完全建立在用户空间中,因此用户线程的创建、切换、析构等操作依然快速(没有发生系统调用),并且可以支持大规模的用户线程并发。而操作系统提供的轻量级进程则作为用户线程和内核线程之间的桥梁,这样可以使用内核提供的线程调度功能及处理器映射,并且用户线程的系统调用要通过轻量级线程来完成,大大降低了整个进程被完全阻塞的风险

在这种混合模式中,用户线程与轻量级进程的数量比是不定的,即为 N:M 的关系。许多 UNIX 系列的操作系统,如 Solaris、HP-UX 等都提供了 N:M 的线程模型实现

对于 Sun JDK 来说,它的 Windows 版与 Linux 版都是使用一对一的线程模型实现的,一个 Java 线程就映射到一条轻量级进程之中,因为 Windows 和 Linux 系统提供的线程模型就是一对一的。
在 Solaris 平台中,由于操作系统的线程特性可以同时支持一对一(通过 Bound Threads 或 Alternate Libthread 实现)及多对多(通过 LWP/Thread Based Synchronization 实现)的线程模型,因此在 Solaris 版的 JDK 中也对应提供了两个平台专有的虚拟机参数:-XX:+UseLWPSynchronization(默认值)和-XX:+UseBoundThreads 来明确指定虚拟机使用哪种线程模型

📓混合线程模型,做到了开发人员和操作系统的友好互助

操作系统的线程调度方式

线程调度是指系统为线程分配处理器使用权的过程。主要的线程调度方式有两种,分别是 协同式线程调度(Cooperative Threads-Scheduling)和 抢占式线程调度(Preemptive Threads-Scheduling)

协同式调度

如果使用协同式调度的多线程系统,线程的执行时间由线程本身来控制,线程把自己的工作执行完了之后,会主动通知系统切换到另外一个线程上

👍优点:实现简单,由于线程要把自己的事情干完后才会进行线程切换,切换操作对线程自己是可知的,所以不存在线程同步的问题

😣Lua 语言中的“协同例程”就是这类实现。它的坏处也很明显:线程执行时间不可控制,甚至如果一个线程编写有问题,一直不告知系统进行线程切换,那么程序就会一直阻塞在那里。

很久以前的 Windows 3.x 系统就是使用协同式来实现多进程多任务的,相当不稳定,如果一个进程坚持不让出 CPU 执行时间就可能会导致整个系统崩溃

独占式调度

使用抢占式调度的多线程系统,每个线程将由系统来分配执行时间,线程的切换不由线程本身来决定(在 Java 中,Thread.yield()可以让出执行时间,但是要获取执行时间,线程本身是没有什么办法的)

👍在这种实现线程调度的方式下,线程的执行时间是系统可控的,不会有一个线程导致整个进程阻塞的问题。Java 使用的线程调度方式就是抢占式调度

与前面所说的 Windows 3.x 的例子相对,在 Windows 9x/NT 内核中就是使用抢占式来实现多进程的,当一个进程出了问题,我们还可以使用任务管理器把这个进程“杀掉”,而不至于导致系统崩溃

线程优先级

虽然 Java 线程调度是系统自动完成的,但是可以建议系统给某些线程多分配一点执行时间,另外的一些线程则可以少分配一点执行时间,可以通过设置线程优先级来完成

✨Java 语言一共设置了 10 个级别的线程优先级(Thread.MIN_PRIORITYThread.MAX_PRIORITY),在两个线程同时处于 Ready 状态时,优先级越高的线程越容易被系统选择执行

🎶Java 线程优先级并不是太靠谱,原因是 Java 的线程是通过映射到系统的原生线程上来实现的,线程调度最终还是取决于操作系统,虽然现在很多操作系统都提供线程优先级的概念,但是操作系统的线程优先级并不是能与 Java 线程的优先级一一对应

如 Solaris 中有 2147483648(232)种优先级,但 Windows 中就只有 7 种,比 Java 线程优先级多的系统还好说,中间留下一点空位就可以了,但比 Java 线程优先级少的系统,就不得不出现几个优先级相同的情况了。Windows 平台的 JDK 中使用了除 THREAD_PRIORITY_IDLE 之外的其余 6 种线程优先级

附录

操作系统线程模型总结