Update 计算机操作系统 - 进程管理.md

为面试中常问的小节加上高频标记;补充进程与线程的对比图和虚拟线程卸载的示意图。
This commit is contained in:
CyC2018
2026-10-30 02:57:57 +08:00
parent 8463d34aa3
commit c95caed832
3 changed files with 10 additions and 6 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

+10 -6
View File
@@ -4,9 +4,9 @@
* [进程与线程](#进程与线程)
* [1. 进程](#1-进程)
* [2. 线程](#2-线程)
* [3. 区别](#3-区别)
* [3. 区别](#3-区别) 🔥🔥
* [4. 上下文切换](#4-上下文切换)
* [5. 协程](#5-协程)
* [5. 协程](#5-协程) 🔥🔥
* [进程状态的切换](#进程状态的切换)
* [进程调度算法](#进程调度算法)
* [1. 批处理系统](#1-批处理系统)
@@ -20,7 +20,7 @@
* [经典同步问题](#经典同步问题)
* [1. 哲学家进餐问题](#1-哲学家进餐问题)
* [2. 读者-写者问题](#2-读者-写者问题)
* [进程通信](#进程通信)
* [进程通信](#进程通信) 🔥🔥
* [1. 管道](#1-管道)
* [2. FIFO](#2-fifo)
* [3. 消息队列](#3-消息队列)
@@ -62,7 +62,7 @@ QQ 和浏览器是两个进程,浏览器进程里面有很多线程,例如 H
需要注意的是,各线程的栈只是分开使用,并没有隔离:拿到另一个线程栈上变量的地址,同样可以读写它。
### 3. 区别
### 3. 区别 🔥🔥
- 拥有资源:进程是资源分配的基本单位,但是线程不拥有资源,线程可以访问隶属进程的资源;
- 调度:线程是独立调度的基本单位,在同一进程中,线程的切换不会引起进程切换,从一个进程中的线程切换到另一个进程中的线程时,会引起进程切换;
@@ -79,6 +79,8 @@ pthread_create() clone(flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_
CLONE_VM 表示共享地址空间,CLONE_FILES 表示共享文件描述符表,CLONE_THREAD 表示加入创建者的线程组。没有这些标志时,子进程得到一份地址空间的副本(写时复制),修改全局变量不影响父进程;有这些标志时,新线程修改的就是同一个变量。getpid() 返回的是线程组 ID,同一进程中的线程都相同,gettid() 返回每个线程自己的 ID,top -H、ps -L 中看到的就是它。
<div align="center"> <img src="pics/process-vs-thread.png" width="760px"/> </div><br>
因此选择多进程还是多线程,主要看更需要隔离还是共享:
- 任务之间需要隔离、一个出错不能拖垮其它任务时用多进程,例如 Chrome 把网页放在单独的渲染进程中,一个网页崩溃不影响浏览器和其它标签页;执行不受信任的代码(例如 Agent 运行模型生成的脚本)也应该放在单独的进程甚至容器中;
@@ -98,7 +100,7 @@ CLONE_VM 表示共享地址空间,CLONE_FILES 表示共享文件描述符表
例如把两个执行流固定在同一个 CPU 上,通过两根管道来回传 1 个字节,让它们交替运行,两个线程之间、两个进程之间每次切换都约 1.1 微秒,包含一次 read 或 write 系统调用(Linux 实测,见[附录 A1](#a1-上下文切换))。数据量很小时两者几乎没有差别,进程切换的额外开销主要在工作集较大时体现出来。线程过多、频繁阻塞时,CPU 会把相当一部分时间花在切换上,可以用 vmstat 的 cs 列或 pidstat -w 观察每秒的切换次数。
### 5. 协程
### 5. 协程 🔥🔥
协程(Coroutine)是在用户态实现的轻量级线程,由程序自己调度,而不是由操作系统内核调度。协程在等待 I/O 时主动让出执行权,运行时再把其它协程调度到同一个线程上执行。
@@ -116,6 +118,8 @@ Go 的 GMP 模型是一个典型实现:G 是 goroutine,M 是系统线程,P
Java 21 正式引入的虚拟线程([JEP 444](https://openjdk.org/jeps/444))就是 JVM 实现的协程:虚拟线程由 JVM 调度到少量的平台线程上运行,执行阻塞 I/O 时从平台线程上卸载,让平台线程去执行其它虚拟线程。调度器是一个工作窃取的 ForkJoinPool,平台线程数默认等于 CPU 核数,JDK 21 不按时间片抢占虚拟线程。虚拟线程卸载时,它的栈帧以栈块对象(stack chunk)的形式保存在堆中,等 I/O 就绪后再挂载到某个平台线程上继续执行,所以阻塞的虚拟线程只占用与调用深度相当的堆内存,不占用平台线程。虚拟线程的创建成本很低,不需要用线程池复用,需要限制并发数时使用信号量。在 JDK 21 中,虚拟线程在 synchronized 块内阻塞时无法卸载,JDK 24 起([JEP 491](https://openjdk.org/jeps/491))基本消除了这个限制。
<div align="center"> <img src="pics/virtual-thread-unmount.png" width="760px"/> </div><br>
例如在 JDK 21 中启动 1 万个阻塞在同一个 CountDownLatch 上的线程(Ubuntu 24.04 容器实测,见[附录 A2](#a2-平台线程与虚拟线程的开销)):
- 平台线程用时约 1.4 秒,进程的常驻内存增加约 290 MB,平均每个约 30 KB,这是因为 2 MB(aarch64)的栈只是预留的虚拟地址,用到的页才占用物理内存;
@@ -482,7 +486,7 @@ void writer() {
这种实现在读者源源不断时可能导致写者饥饿,写者优先和按到达顺序排队的公平实现见[进阶:读者-写者问题](计算机操作系统%20-%20进程管理%20-%20进阶.md#读者-写者问题)。
## 进程通信
## 进程通信 🔥🔥
进程同步与进程通信很容易混淆,它们的区别在于: