购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

第2章
操作系统多线程基础

本章主要对操作系统多线程的相关知识进行回顾和总结,包括线程的概念、线程与进程的区别以及多线程会面临的一些问题、挑战,以便读者能够更好理解后续章节会介绍到的Java多线程编程的相关知识。

操作系统作为计算机硬件资源的管理者,所有应用程序都需要运行它之上,统一由它来为应用程序的运行分配CPU、内存、磁盘等硬件资源。其中CPU负责处理所有的计算任务,是计算机最核心的硬件资源,也是与应用程序并发处理能力最直接相关的硬件资源,所以高效利用CPU资源是实现高并发应用程序需要重点考虑的因素。

现代的计算机系统一般都是采用多核CPU架构,所以Java高并发应用系统设计的一个最基本也是最重要的手段,就是通过Java多线程设计来高效利用CPU的多个核心,实现请求的并发和并行处理,提高应用程序的并发处理能力。由于Java多线程也是需要依赖底层操作系统的线程来实现,所以在讲解Java多线程之前,先在本章对操作系统多线程的一些核心知识进行分析,以便读者能够更好地理解后面章节会介绍的Java并发编程中涉及的多线程设计、多线程并发与线程安全的相关知识点。

2.1 线程概念

现代操作系统都是多任务操作系统,多任务是指允许多个任务在操作系统上同时运行。其中任务对应到操作系统的专业术语就是进程,所以也可以说是允许多个进程在操作系统上同时运行。进程是一个动态的概念,代表的是程序代码在一个数据集合上的一次运行过程。而每个进程内部可以包含多个线程,每个线程代表的是当前进程的一个单一顺序控制流,不同线程可以同时执行来处理不同的操作,从而实现并发性,其中线程也是CPU调度执行的最小单元。

除此之外,由于操作系统也是一个运行于计算机之上的软件系统,而每个应用系统又是运行于操作系统之上,所以每个应用线程都需要基于一个操作系统线程来获取CPU资源并执行,应用线程与操作系统线程之间的映射关系也称为操作系统的多线程模型。

2.1.1 多任务调度

操作系统允许多个任务同时运行,任务的执行最终都需要交给底层CPU的某个核心来执行,而CPU核心的数量一般都是少于需要执行的任务数量,所以操作系统需要负责对这些任务进行调度,使得每个任务都可以在CPU的某个核心执行。

大部分的操作系统实现,如Linux、Windows等,都是采用时间片轮转的抢占式调度方式来实现任务调度,即每个任务在CPU的某个核心执行一小段时间后会被强制暂停,然后换另一个任务继续,按照这个规律实现多个任务的轮流执行。这一小段时间就是抢占式调度的时间片。由于每个时间片非常短且每个任务的执行速度都非常快,给用户的感觉是所有任务都是同时执行的,所以这也称为并发执行。

单个CPU核心上的任务执行情况如图2.1所示,任务A和任务B在同一个CPU核心轮流执行,其中R表示在运行,I表示空闲等待。

图2.1 单核CPU的任务执行

以上介绍的时间片轮转调度的执行粒度是针对CPU的单个核心而言的,对于多核CPU的多个核心,则可以在每个核心都独立进行时间片轮转调度来执行任务。在不同核心执行的任务是可以真正做到同时执行的,这种执行就是并行执行。

多核CPU上多个任务的并行执行情况如图2.2所示,任务A和任务B可以同时在两个不同的CPU核心中执行。不过由于每个CPU核心可能还存在其他任务,所以任务A和任务B有同时处于暂停的状态。

图2.2 多核CPU的任务执行

2.1.2 线程与进程的关系

由以上对多任务调度的分析可知,在同一个CPU核心执行的多个任务是并发执行的,每个任务执行一小段时间后就需要被暂停,然后在之后的某个时间点重新获得CPU时间片继续执行。为了保证任务执行的连贯性,在这个过程中需要对任务的执行状态进行保存,以便后续能够接着之前的执行状态继续执行,这个执行状态也称为上下文,如线程上下文、进程上下文等。

最初的操作系统设计是一个任务对应一个进程,每个进程都会被分配运行所需要的相关硬件资源,如内存、打开的文件描述符等。在运行过程中,当进程的CPU时间片用完时,则需要暂停进程和对该进程所使用的内存等资源的状态进行保存,而这个工作也是需要CPU来完成的。所以当操作系统的进程非常多时,则需要频繁地进行进程的暂停和对进程所使用的内存等资源的状态进行保存,导致耗费大量的CPU时间,而实际进行有效任务计算的CPU时间大大减少,造成任务执行缓慢,系统性能降低。

为了降低进程切换时CPU用于进程状态保存的时间开销,需要设计一种更加轻量级的进程实现,这种实现就是线程。在一个进程内部可以包含多个线程,这些线程共享其所属进程的内存、打开的文件描述符等硬件资源。由于每个线程是共享进程的硬件资源,自身只需要占用非常少的内存来维护自身的运行时状态,所以线程是一种更加轻量级的进程实现。

有了线程之后,进程只作为系统硬件资源分配的最小单元,不再作为CPU调度执行的最小单元,而是将线程作为CPU调度执行的最小单元。这样的好处是CPU可以将时间片分给线程来执行,当线程的时间片用完需要被暂停时,则只需要保存线程独立的内存状态即可,这相对于保存进程的各种硬件资源的状态而言,CPU资源开销少非常多。所以这就实现了CPU时间可以更多地用在有效的任务计算方面,而不是处理进程切换的状态维护,从而加快了任务的处理速度,提高了系统的整体性能。

除线程切换开销更小外,使用线程的另一个好处是可以充分利用CPU的多个核心。因为每个进程内部都可以包含多个线程,每个线程是该进程的一个独立顺序控制流。当CPU资源充足时,这多个线程可以同时独立运行在不同的CPU核心中,实现真正的并行执行,加快任务的处理速度,从而进一步提高系统的并发处理性能。

关于进程与线程的关系以及两者的内部核心实现如图2.3所示。

进程主要包含程序代码、数据集、进程控制块和打开的文件描述符四大运行时数据。在进程内部包含多个线程,这些线程共享该进程的以上运行时数据。每个线程在内部只包含寄存器、方法调用栈和程序计数器(PC)。所以相对于进程而言,线程所需维护的状态更少,更加轻量级,在进行线程上下文切换时的开销更小。

在采用了线程作为CPU调度执行的最小单元之后,对应到2.1.1节示意图的任务A和任务B就是两个不同的线程。有了多线程的概念之后,一个进程可以有多个线程同时执行,而不再是所有操作都在该进程中串行执行,从而提高了单个进程的并发处理能力。

图2.3 进程与线程的关系及其内部核心实现

2.1.3 多线程模型

由操作系统的知识可知,应用系统运行在操作系统之上,应用线程不能直接访问CPU等硬件资源,需要通过操作系统来间接访问。计算机、操作系统与应用系统的组成关系如图2.4所示。

图2.4 计算机、操作系统与应用系统的组成关系

从以上的计算机、操作系统与应用系统的组成关系可知,每个应用系统线程,或者称为用户线程的执行需要通过操作系统映射到一个操作系统线程,或者称为内核线程,通过内核线程来间接访问CPU等资源。

由于操作系统上面运行着多个应用程序,每个应用程序进程内部可以包含多个用户线程,所以内核线程的数量通常少于用户线程的数量。为了解决这个线程数量差异的问题,需要设计一种用户线程和内核线程之间的映射关系来解决用户线程的调度执行问题,使得每个用户线程都可以关联到一个内核线程来执行,这种映射关系就是操作系统的多线程模型。

操作系统的多线程模型一般包含三种类型,分别为多对一模型、一对一模型和多对多模型。

多对一模型是指所有用户线程映射到同一个内核线程,这个内核线程再关联到一个CPU核心。

多对一线程模型的映射关系如图2.5所示,线程1、线程2和线程3都映射到同一个内核线程,这个内核线程再使用一个CPU核心来执行。

图2.5 多对一线程模型

多对一模型是早期操作系统使用的一种多线程模型,因为早期计算机的CPU一般只有一个核心。多对一模型的好处是可以实现用户线程由应用系统自身的线程库来灵活管理,缺点是如果某个用户线程阻塞了,则会一直占用这个内核线程,导致其他用户线程无法继续执行。

除此之外,这种模型还存在一个问题就是在多核CPU架构下,这种模型还是只能使用一个CPU核心,无法同时利用CPU的其他核心来实现任务的并行处理。所以这种模型的并发性较差,现在操作系统一般不会使用这种多线程模型。

为了解决多对一模型存在的阻塞调用和无法使用CPU多核的问题,出现了一对一模型。一对一模型是每个用户线程对应到一个内核线程。在这种模型中,某个用户线程的阻塞不会影响到其他线程的执行,因为其他线程也有对应的内核线程来执行。同时这多个内核线程可以在CPU的多个核心同时执行,实现并行处理。所以一对一模型既解决了阻塞问题,也提高了CPU资源的利用率。相对于多对一模型,一对一模型的并发性能更高。

一对一模型的线程映射关系如图2.6所示,线程1、线程2、线程3都会使用一个独立的内核线程来执行,操作系统需要为每个用户线程创建一个内核线程。

图2.6 一对一线程模型

不过在一对一模型中,由于每个用户线程都需要使用一个内核线程,当用户线程较多时,会导致需要频繁创建内核线程。而创建内核线程的资源开销是较大的,频繁进行内核线程的创建会反过来影响用户线程的执行性能,所以这种模型一般会限制能够创建的最大线程数量,从而避免资源的过度使用。

为了进一步优化多线程模型,解决多对一模型和一对一模型存在的问题,后来出现了多对多模型。多对多模型主要是实现了内核线程的复用,避免内核线程的频繁创建和销毁,使得每个内核线程可以处理多个用户线程的执行请求。

在具体实现层面,每个内核线程都可以处理多个用户线程的执行请求,多个用户线程也可以在不同的内核线程上处理,从而实现了一种多路复用的机制,即多个用户线程可以基于同等数量或者更少数量的内核线程来执行。

多对多线程模型,既可以充分利用CPU的多个核心来实现线程的并发或并行处理,解决多对一模型中阻塞和无法使用多核CPU的问题;又实现了内核线程的复用,解决了一对一模型中在用户线程过多时,内核线程的创建开销大,影响整体性能的问题。现代的操作系统大多使用多对多线程模型,其具体运作情况如图2.7所示,线程2和线程3可以复用同一个内核线程来执行。

图2.7 多对多线程模型

以上介绍这三种多线程模型的主要目的是希望读者能够理解每个用户线程是如何通过操作系统这层的处理,最终在CPU执行的。与此同时,需要注意CPU的核心数量通常是有限的,所以不是创建越多的内核线程,应用程序就运行得越快,因为内核线程的创建也存在开销,所以要结合机器的性能高低和压力测试来决定最佳的内核线程数量。 sPsEJMynVFtnafcswRyA9IXbSQW4UeG3J9r+nRiJ5N8gfoyqJD0bLmQnw1MHeIUF

点击中间区域
呼出菜单
上一章
目录
下一章
×