2025-01-15🌱上海: ☀️ 🌡️+4°C 🌬️↓14km/h

# 线程和进程有什么区别?

比较项目 进程 线程
定义 资源分配的基本单位,可看作正在运行的程序实例,每个进程有独立内存空间(代码段、数据段、堆栈等),进程间相互独立 CPU 调度的基本单位,属于进程,一个进程可包含多个线程,线程共享进程内存空间和资源(如文件句柄、数据段),但有自己独立的栈和寄存器
资源消耗 创建时需分配独立内存空间和系统资源,创建与切换开销较大 共享进程资源,创建开销小,切换开销远小于进程切换
通信方式 因内存空间相互独立,进程间通信(IPC)复杂,需借助管道、消息队列、共享内存、套接字等方式 同一进程内线程共享内存空间,直接读写内存,需同步机制避免数据错误

# 进程

代码最初是存储于硬盘的静态文件,经过编译生成二进制可执行文件。运行该文件时,其被载入内存,CPU 执行指令,此运行中的程序即 “进程”。

当进程中有读取硬盘文件数据的操作时,因硬盘读写速度慢,若 CPU 等待硬盘返回数据,利用率会很低。所以当进程要从硬盘读取数据的时候,CPU 会先去执行其他的进程,等到硬盘数据读取完返回时,CPU 会收到一个终端,然后 CPU 再继续运行这个进程。

image.png

多个程序、交替执行的思想,就有 CPU 管理多个进程的初步想法

单核的 CPU 在某一瞬间,只能运行一个进程,但是在短时间内,它可能会运行多个进程,这样就会产生并行的错觉,实际上这是并发

# 并行和并发有什么区别?

并行就像是你只有一个工人(单核 CPU),但他要干好多活,他就会先干一会儿这个活,然后放下,再去干一会儿那个活,不停地在不同的活之间换来换去,看起来好像他同时在干好几样活似的。

并发就好比你有好几个工人(多个处理器),他们可以同时干活,而且是在同一时刻一起干活哦 比如说你要盖几栋房子,有四个工人(四核 CPU),他们可以同时盖四栋房子,每个人负责一栋,互不干扰,一起推进工作。

image.png

当只有一个处理器(就像这个工人)在处理多个任务时,它会在不同的任务之间快速切换,每次切换都要保存当前任务的状态,这样它回来继续处理这个任务时,才能接着之前的进度继续。比如说,你正在用手机听歌,同时还在浏览网页,手机处理器就会在这两个任务之间切换。当你从听歌切换到浏览网页时,处理器会保存听歌的进度、播放的位置等信息,然后开始处理网页浏览的任务;等你又切回听歌时,处理器会根据之前保存的信息,让歌曲接着播放,不会从头开始。

从上面可以看出,进程有着运行 - 暂停 - 运行的活动规律。

# 进程的状态

# 三种基本状态

在一个进程的活动期间至少具备三种基本状态,即运行状态、就绪状态、阻塞状态。

image.png

上图所示的状态:

  • 运行状态 (Running):该时刻进程占用 CPU。
  • 就绪状态 (Ready):可运行,因其他进程处于运行状态而暂时停止运行。
  • 阻塞状态 (Blocked):进程等待某一事件发生(如等待输入 / 输出操作完成)而暂时停止运行,此时即便获得 CPU 控制权也无法运行。

# 五种基本状态

image.png

上图多了两个基本状态:

  • 创建状态 (new):进程正在被创建时的状态。
  • 结束状态 (Exit):进程正在从系统中消失时的状态。

以上的状态构成了一个完整的进程状态变迁,详细说明一下进程的状态变迁:

  • NULL 到创建状态:新进程创建时首个状态。
  • 创建状态到就绪状态:进程创建完成并初始化,一切就绪后很快进入就绪状态。
  • 就绪态到运行状态:进程调度器选中就绪态进程,分配 CPU 使其正式运行。
  • 运行状态到结束状态:进程运行完成或出错,被操作系统处理为结束状态。
  • 运行状态到就绪状态:运行进程时间片用完,操作系统将其变为就绪态,另选就绪态进程运行。
  • 运行状态到阻塞状态:进程请求如 I/O 等需等待的事件时进入阻塞状态。
  • 阻塞状态到就绪状态:进程等待的事件完成,从阻塞状态变为就绪状态。

但是如果有大量的进程处于阻塞状态,会占用过多的物理内存空间,为了避免这种浪费物理内存的行为,在虚拟内存管理的操作系统中,通常会把阻塞状态的进程的物理内存空间换到硬盘中,等需要再次运行的时候,再换入到物理内存

image.png

因此就需要一个挂起状态来描述进程没有占用实际的物理内存空间的情况

挂起状态可以分为两种:

  • 阻塞挂起状态:进程在外存 (硬盘) 并等待某个事件的出现;
  • 就绪挂起状态:进程在外存 (硬盘),但只要进入内存,即刻立刻运行

image.png

导致进程挂起的原因,除进程使用的内存空间不在物理内存外,还包括:

  • 通过 sleep 实现间歇性挂起:利用设置定时器,定时器到期后唤醒进程。
  • 用户主动挂起:例如在 Linux 系统中,用户可通过 Ctrl+z 操作挂起进程。

# 进程的控制结构

PCB(process control block 进程控制块) 是进程存在的唯一标识,进程消失则 PCB 随之消失。其包含的信息如下:

  • 标志信息:进程标识符用于标识进程,分为两种:

  • 外部标识符:由进程创建者命名,一般由字母、数字组成字符串,方便用户(进程)访问进程时使用,具有易记忆的特点,例如计算进程、打印进程等。

  • 内部标识符:为方便系统使用而设,操作系统为每个进程赋予唯一整数作为内部标识符,通常就是一个进程序号。

  • 说明信息(进程调度信息):与进程调度相关的状态信息,具体包含:

  • 进程状态:表明进程当前所处状态,是进程调度和对换的重要依据。

  • 优先权:优先权高的进程能优先获取处理器资源。

  • 其他调度信息:其内容取决于所采用的进程调度算法,例如进程等待时间、已运行时间等。

  • 等待事件:指进程从运行状态转变为等待状态时所等待发生的事件,即等待原因。

  • 现场信息(处理器状态信息):用于保留进程在处理器中的各类信息,主要由处理器的各寄存器内容构成。进程暂停运行时,寄存器信息存入 PCB,重新运行时可从上次停止处继续。具体包括:

  • 通用寄存器:其中内容可被用户程序访问,用于临时存储信息。

  • 指令计数器:存放即将访问的下一条指令的地址。

  • 程序状态字:保存当前处理器的状态信息,如运行方式、中断屏蔽标志等。

  • 用户栈指针:每个用户进程有一个或多个与之相关的栈,用于存放过程和系统调用参数及调用地址,栈指针指向堆栈栈顶。

  • 管理信息(进程控制信息):包含进程运行所需的进程资源、控制机制等信息:

  • 程序和数据的地址:指进程的程序和数据在主存和外存的地址,以便进程再次运行时能找到相应程序和数据。

  • 进程同步和通信机制:指实现进程同步和通信所采用的机制,如消息队列指针、信号量等。

  • 资源清单:记录除 CPU 外,进程所需的全部资源以及已分配到的资源。

  • 链接指针:指向该进程所在队列下一个进程 PCB 的首地址。

PCB 是如何组织的呢?

通常以链表方式组织进程,将相同状态的进程链成各种队列:

  • 就绪队列:把所有处于就绪状态的进程链在一起形成。
  • 阻塞队列:将所有因等待某事件而处于等待状态的进程链在一起组成。
  • 运行队列:在单核 CPU 系统中,只有一个运行指针,因为单核 CPU 同一时间只能运行一个程序。

image.png

image.png

除了链接的组织方式,还有索引方式,它的工作原理:将同一状态的进程组织在一个索引表中,索引表项指向相应的 PCB,不同状态对应不同的索引表。

image.png

一般会选择链表,因为可能面临进程创建,销毁等调度导致进程状态发生变化,所以链表能够更加灵活的插入和删除。

# 进程的控制

操作系统内核相关概念

  • 核心态

  • 别称管态、系统态,是操作系统管理程序执行时计算机所处状态。

  • 此状态特权高,能够执行所有指令,可访问全部寄存器与存储器。

  • 用户态

  • 又称目态,是用户执行程序时计算机所处状态。

  • 特权较低,仅能执行规定指令,访问指定的寄存器和存储器。

  • 用户态与内核态区别

  • 是操作系统的两种运行级别,用户态为应用程序运行状态,内核态为操作系统运行状态。

  • 用户态下,应用程序可访问自身内存空间及部分受限系统资源,但无法直接访问硬件设备与操作系统核心功能。

  • 内核态下,操作系统能访问所有系统资源与硬件设备,并执行所有核心功能。

  • 用户态与内核态切换

  • 通过系统调用、异常和外围设备中断实现。

  • 当应用程序需访问受限系统资源或执行核心功能,发起系统调用,操作系统将其切换到内核态执行操作,之后再切换回用户态让应用程序继续执行。

  • 由于切换过程需进行保存和恢复现场、复制参数和代码等操作,所以开销较大。

特权指令和非特权指令

  • 特权指令

  • 是在系统态下运行的指令,等同于系统指令。

  • 对内存访问不受限制,可访问用户空间与系统空间,禁止应用程序使用。

  • 这类指令需较高执行权限,一般由操作系统或内核执行,涉及系统资源管理与控制,像内存管理、I/O 操作、中断处理等。

  • 只有在特殊特权级别下才能执行,常见的有:

  • 操作系统调用指令。

  • 修改页表的指令。

  • 中断和异常处理指令。

  • I/O 操作相关指令。

  • 修改控制寄存器的指令。

  • 非特权指令

  • 是在用户态运行的指令,供应用程序使用。

  • 访问内存受限,仅能访问用户空间,不能直接访问系统软硬件。

  • 由普通用户程序直接执行,通常涉及一般计算、数据传输和逻辑运算等基本操作,执行时不影响系统关键资源,可被用户程序自由使用。常见的有:

  • 数据移动指令,如 MOV。

  • 算术运算指令,如 ADD、SUB。

  • 逻辑运算指令,如 AND、OR。

  • 条件分支指令,如 JUMP。

  • 栈操作指令,如 PUSH、POP。

  • 指令区分意义:将特权指令和非特权指令分开,可使计算机体系结构实现更好的安全性与系统稳定性,确保只有授权代码能执行对系统关键资源的敏感操作。

# 进程创建

  • 创建场景

  • 用户登录:在分时系统中,用户键入合法登录命令后,系统为该终端用户建立进程并放入就绪队列。

  • 作业调度:批处理系统里,作业调度程序调度作业时,将作业装入主存,分配资源并创建进程,放入就绪队列。

  • 提供服务:运行中的用户进程提出请求,系统专门创建进程提供所需服务。

  • 应用请求:应用进程基于自身需要创建新进程,即子进程。

  • 处理过程

一旦操作系统发现了要求创建进程的事件后,便调用进程创建原语,按照下列步骤创建一个新进程:

image.png

# 进程撤销

  • 撤销原因

  • 进程正常结束:如 C 语言程序函数执行到最后一条 return 指令,程序运行结束。

  • 进程异常错误:进程运行中出现越界错误、超时故障、非法指令错、运行超时、等待超时、算术运算错、I/O 故障等,被迫中止。

  • 进程应外界请求终止:例如操作员、操作系统要求父进程干预或父进程结束等情况。

  • 处理过程

进程撤销的处理过程,一旦操作系统发现了要求终止进程的事件后,便调用进程终止原语,按照下列步骤终止指定的进程:

image.png

  • 进程等待(进程阻塞)

    • 等待原因

      • 请求系统服务:如运行进程申请打印机打印,因资源被占而只能等待。
      • 启动某种操作:进程启动操作后,后续命令需操作完成才能运行,故先等待。
      • 新数据尚未到达:相互合作的进程,一进程需另一进程提供数据,数据未到则等待。
      • 无新工作可做:特定功能的系统进程完成任务后,阻塞等待新任务。
  • 处理过程

进程等待的处理过程,一旦操作系统发现了要求等待进程的事件后,便调用进程等待原语,按照下列步骤阻塞指定的进程:

image.png

  • 进程唤醒

    • 唤醒原因

      • 请求系统服务得到满足:因请求服务不满足而等待的进程,得到服务时被唤醒。
      • 启动某种操作完成:等待操作完成的进程,操作完成后可执行后续命令,被唤醒。
      • 新数据已经到达:相互合作进程中,等待数据的进程,在数据到达时被唤醒。
      • 有新工作可做:特定功能的系统进程接收到新任务,被唤醒。
  • 处理过程

进程唤醒的过程,一旦操作系统发现了要求唤醒进程的事件后,便调用进程唤醒原语,按照下列步骤唤醒指定的进程

image.png

# 进程的上下文切换

# 进程上下文切换的定义

各个进程共享 CPU 资源,不同时刻进程需切换以使不同进程能在 CPU 执行,从一个进程切换到另一个进程运行,这一过程称为进程的上下文切换。

大多数操作系统为多任务系统,通常可支持多于 CPU 数量的任务同时运行。但实际上这些任务并非真正同时运行,而是系统在短时间内让各个任务轮流在 CPU 上运行,从而营造出同时运行的错觉。

任务由 CPU 运行,每个任务运行前,CPU 需明确任务的加载位置与起始运行位置。因此,操作系统要预先为 CPU 设置好 CPU 寄存器和程序计数器

CPU 寄存器是 CPU 内部容量小但速度极快的内存(缓存)。CPU 寄存器和程序计数器是 CPU 运行任何任务前必须依赖的环境,这些环境被称为 CPU 上下文

CPU 上下文切换是指先保存前一个任务的 CPU 上下文(即 CPU 寄存器和程序计数器),接着将新任务的上下文加载到这些寄存器和程序计数器中,最后跳转到程序计数器所指的新位置,开始运行新任务。系统内核会存储保存下来的上下文信息,当该任务再次被分配给 CPU 运行时,CPU 重新加载这些上下文,确保任务原有状态不受影响,使其看起来像是连续运行的。

上述提到的 “任务” 主要包括进程、线程和中断。因此,根据任务类型的不同,CPU 上下文切换可分为进程上下文切换、线程上下文切换和中断上下文切换。

进程的上下文切换到底是切换什么?

进程由内核管理与调度,其切换只能在内核态发生。进程上下文切换涵盖用户空间资源,如虚拟内存、栈、全局变量等,以及内核空间资源,像内核堆栈、寄存器等。一般将交换信息保存于进程的 PCB,当运行其他进程时,从该进程 PCB 中取出上下文并恢复到 CPU,以使进程能继续执行。
image.png

发生进程上下文切换的场景

  • 为保证公平调度,CPU 时间划分为时间片并轮流分配给各进程,当某进程时间片耗尽,从运行态变为就绪态,系统从就绪队列选另一进程运行。
  • 进程因系统资源不足(如内存不足),需等资源满足才可运行,此时会被挂起,由系统调度其他进程。
  • 进程通过睡眠函数 sleep 等主动挂起自己,会触发重新调度。
  • 有优先级更高的进程时,为保证其运行,当前进程会被挂起,让高优先级进程运行。
  • 发生硬件中断,CPU 上的进程被中断挂起,转而去执行内核中的中断服务程序。

# 线程

进程中的一个执行任务(控制单元),负责当前进程中程序的执行。一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可共享数据。

与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程

Java 程序天生就是多线程程序,我们可以通过 JMX 来看一下一个普通的 Java 程序有哪些线程,代码如下。

public class MultiThread {
	public static void main(String[] args) {
		// 获取 Java 线程管理 MXBean
		ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
		// 不需要获取同步的 monitor 和 synchronizer 信息,仅获取线程和线程堆栈信息
		ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
		// 遍历线程信息,仅打印线程 ID 和线程名称信息
		for (ThreadInfo threadInfo : threadInfos) {
			System.out.println("[" + threadInfo.getThreadId() + "] " + threadInfo.getThreadName());
		}
	}
}

程序输出如下:

[6] Monitor Ctrl-Break // 监听线程转储或 “线程堆栈跟踪” 的线程
[5] Attach Listener // 负责接收到外部的命令,而对该命令进行执行的并且把结果返回给发送者
[4] Signal Dispatcher // 分发处理给 JVM 信号的线程
[3] Finalizer // 在垃圾收集前,调用对象 finalize 方法的线程
[2] Reference Handler // 用于处理引用对象本身(软引用、弱引用、虚引用)的垃圾回收的线程
[1] main //main 线程,程序入口

从上面的输出内容可以看出:一个 Java 程序的运行是 main 线程和多个其他线程同时运行

# 多线程进程

在多线程模型中,每个线程共享 code、data、files,但每个线程又有独立的寄存器和栈

采用多线程的优点:

响应性:服务器可以为每个请求创建一个线程来响应

资源共享:一个进程下的所有线程都可以访问该进程的共享资源 (比如全局变量和静态变量)

经济:创建线程不需要像创建进程一样为公共资源分配额外的内存,所有线程共享

可伸缩性:多线程可以在单个核 CPU 上并发执行,也可以在多核 CPU 上并发和并行执行

image.png

在多处理器系统中 (现在基本都是多核处理器),多核编程机制让应用程序可以更有效地将自身的多个执行任务 (并发线程) 分配到不同的处理器上运行,以实现并行运算

image.png

# 从 JVM 角度说进程和线程之间的关系(重要)

image.png

从上图可以看出:一个进程中可以有多个线程,多个线程共享进程的方法区 (JDK1.8 之后的元空间) 资源,但是每个线程有自己的程序计数器虚拟机栈本地方法栈

# 线程的上下文切换

线程上下文切换情况取决于线程是否属于同一进程:

  • 若两个线程不属于同一个进程,其切换过程与进程上下文切换类似。
  • 若两个线程属于同一个进程,由于虚拟内存共享,切换时虚拟内存等资源保持不变,仅需切换线程私有数据、寄存器等非共享数据。因此,相比进程,线程的上下文切换开销小很多。

# 线程的实现

线程实现方式主要有三种

  • 用户线程(User Thread):在用户空间实现,由用户态线程库管理,并非内核管理的线程。
  • 内核线程(Kernel Thread):于内核中实现,由内核负责管理。
  • 轻量级进程(LightWeight Process):在内核中用于支持用户线程。

需考虑用户线程与内核线程的对应关系,第一种为多对一关系,即多个用户线程对应同一个内核线程。

image.png

第二种是一对一的关系,就是一个用户线程对应一个内核线程

image.png

第三种就是多对多的关系,多个用户线程对应多个内核线程

image.png

# 用户线程的理解

用户线程基于用户态线程管理库实现,其 ** 线程控制块(Thread Control Block,TCB)** 也在库中,操作系统看不到 TCB,仅能看到进程的 PCB。因此,操作系统不直接参与用户线程的管理与调度,而是由用户级线程库函数负责,涵盖线程创建、终止、同步及调度等操作。用户级线程模型类似多对一关系,即多个用户线程对应同一个内核线程。

image.png

优劣分析 详情
优点 1. 每个进程有私有 TCB 列表,由用户级线程库函数维护,可用于不支持线程技术的操作系统 <br>2. 线程切换由线程库函数完成,无需用户态与内核态切换,速度快
缺点 1. 操作系统不参与调度,若一线程发起系统调用阻塞,进程内其他用户线程都无法执行 <br>2. 线程运行后,除非主动交出 CPU 使用权,否则进程内其他线程无法运行,因用户态线程无打断当前线程特权 <br>3. 时间片分配给进程,多线程执行时,每个线程获得时间片少,执行慢

# 内核线程的理解

内核线程是由操作系统管理的,线程对应的 TCB 自然是放在操作系统里的,这样线程的创建、终止和管理都是由操作系统负责。

内核线程的模型,也就类似前面提到的一对一的关系,即一个用户线程对应一个内核线程,如下图所示:

image.png

优劣分析 详情
优点 1. 进程中某内核线程因系统调用阻塞,不影响其他内核线程运行 <br>2. 时间片分配给线程,多线程的进程可获更多 CPU 运行时间
缺点 1. 在支持内核线程的操作系统中,内核维护进程和线程的上下文信息(如 PCB 和 TCB) <br>2. 线程的创建、终止和切换通过系统调用进行,系统开销较大

# 轻量级进程如何理解

** 轻量级进程(****Light-weight process,****LWP)是内核支持的用户线程。一个进程可包含一个或多个 LWP,每个 LWP 与内核线程一一映射,** 由内核管理并像普通进程一样被调度。在多数系统中,LWP 和普通进程的区别在于,LWP 仅拥有最小执行上下文以及调度程序所需的统计信息。通常,进程代表程序实例,LWP 代表程序执行线程,因执行线程所需状态信息少于进程,LWP 不携带过多此类信息。

在轻量级进程(LWP)之上可使用用户线程,LWP 与用户线程存在三种对应关系:

  • 1:1:一个 LWP 对应一个用户线程。
  • N:1:一个 LWP 对应多个用户线程。
  • M:N:多个 LWP 对应多个用户线程 。

image.png

模式 对应关系 优点 缺点
1:1 模式 一个线程对应一个 LWP,再对应一个内核线程(如进程 4) 可实现并行,一个 LWP 阻塞不影响其他 LWP 每创建一个用户线程就产生一个内核线程,创建线程开销大
N:1 模式 多个用户线程对应一个 LWP,再对应一个内核线程(如进程 2),线程管理在用户空间完成,用户线程对操作系统不可见 可随意开启多个用户线程,上下文切换在用户空间,效率较高 一个用户线程阻塞会导致整个进程阻塞,多核 CPU 资源无法充分利用
M:N 模式 多个用户线程对应多个 LWP,LWP 再一一对应到内核线程(如进程 3),结合前两种模型形成,提供两级控制 综合前两种模式优点,多数线程上下文切换在用户空间,且能充分利用多核 CPU 资源 /
组合模式 结合 1:1 模型和 M:N 模型(如进程 5) 开发人员可针对应用特点调节内核线程数目,实现物理并行性和逻辑并行性的最佳方案 /