引入
程序本身不会执行,会由程序员放在硬盘中永久保存。要使用程序的时候,CPU 就会从硬盘中读取程序到内存中,方便使用,那么这个在内存中运行的程序就叫做进程。当一个进程被初始化时,操作系统会自动为其创建第一个线程(称为 “主线程”),后续线程则由主线程或其他线程通过系统调用创建,且创建时必须指定所属进程。也就是一个进程下会有一个或者多个线程负责共同执行任务,这些线程不拥有独立的资源空间,所有线程共享所属进程的全部资源。

进程是内存中正在运行的程序,每个进程都有自己独立的一块内存空间(包含代码、数据、全局变量及打开的文件句柄等资源),一个进程可以有多个线程,这些线程共享进程的内存空间和资源,却各自拥有独立的执行路径。比如在 Windows 系统中,一个运行的 chrome.exe 程序就是一个进程,而它内部同时进行的页面渲染、网络请求、插件运行等任务,便是由多个线程分别负责 —— 这些线程共享 Chrome 进程的内存资源,却能并行或并发地推进不同任务,让浏览器既流畅显示页面,又能同时下载文件。
一个程序若重复使用,则会多次被读取到内存中并运行时,成为多个独立的进程。

每个进程都有一个加载程序,通常只有一个程序计数器,记录当前程序执行的位置;然后程序会按照其执行顺序进行计算。这里的一个执行流就是一个线程。如果有多个线程,就需要多个程序计数器。每个线程独自运行,并具有寄存器、堆栈等程序运行的状态信息。同时,线程间共享的则有地址空间、全局变量、打开的文件等等信息。

进一步形象化进程与线程概念
想象一家餐厅的后厨就是一个计算机系统。
- 进程 就像是一个 独立的、功能齐全的厨房包间。每个包间都有自己的冰箱(内存)、储物柜(资源)、厨具和专属的菜谱(程序代码)。餐厅可以同时租用多个包间给不同的团队(运行多个进程),他们互不干扰。一个包间出了问题,不会影响其他包间。
- 线程 就像是同一个厨房包间里的 多名厨师。他们共享这个包间里的所有资源(共用同一个冰箱、同一个储物柜)。他们需要协作完成一道大菜(一个任务)。协作效率很高,因为沟通方便(数据共享),但必须小心协调,避免两个人同时抢同一把刀(需要“锁”来同步),否则会出乱子。如果其中一位厨师不小心把整个包间点着了(一个线程发生严重错误),那么包间里所有厨师的工作都会中断(整个进程崩溃)。
- CPU 则像是厨房里的 炉灶和操作台。一个多核CPU就像是有多个炉灶。操作系统(餐厅经理)负责安排哪些厨房包间(进程)的哪些厨师(线程)在哪个炉灶(CPU核心)上工作,并且会频繁地切换,造成“同时进行”的假象。

基本定义
下面归纳一下进程与线程的定义
进程是操作系统资源分配的基本单位。它是程序的一次执行过程,拥有独立的内存空间。一个进程可以包含多个线程,它们共享进程的资源,但是每个线程有自己的执行路径。
线程是进程中的一个执行任务,是处理器任务调度和执行的基本单位。它是比进程更小的能独立运行的基本单位。线程共享其所属进程的地址空间和资源,但拥有自己的程序计数器、虚拟机栈和本地方法栈。线程之间的切换开销小于进程,因此被称为轻量级进程。
关键词说明
执行与调度相关概念
地址空间概念: 操作系统为每个进程分配的、独立的、受保护的虚拟内存范围。可以理解为一个进程所能使用的所有内存地址的集合。为何重要: 这是进程间隔离的基石。进程A不能直接访问进程B的地址空间,从而保证了安全性和稳定性。线程则共享其所属进程的同一个地址空间。
资源概念: 程序运行时所需的各类实体,主要包括:内存: 存放代码和数据。I/O设备: 如打开的文件、网络连接。CPU时间: 被调度执行的权利。为何重要: 进程是资源分配的基本单位。系统为进程整体分配资源(如内存、文件句柄)。线程作为进程的一部分,共享这些已分配的资源。
资源与隔离相关概念
调度: 操作系统的“调度程序”决定接下来该轮到哪个任务(进程或线程)使用CPU。这是一个“选择”的过程,基于优先级、等待时间等算法。
分派: 调度做出选择后,“分派程序”负责具体的切换动作,比如保存当前任务的上下文、加载下一个任务的上下文,然后让CPU开始执行它。为何重要: 线程是CPU调度和分派的基本单位。操作系统直接看到并调度的是线程,而不是整个进程。这就解释了为什么线程切换开销更小(要保存和恢复的上下文信息更少)。
上下文切换概念: 将CPU从一个任务(进程/线程)切换到另一个任务时,必须保存当前任务的状态(上下文),并加载下一个任务的状态的过程。上下文包括寄存器值、程序计数器等。为何重要: 进程的上下文切换涉及内存地址空间的切换(如更换页表),开销巨大。线程的上下文切换发生在同一地址空间内,开销要小得多。 这是线程“轻量”的关键原因
xmind 导图




对比小结
| 特性 | 进程 | 线程 |
|---|---|---|
| 基本单位 | 资源分配的基本单位 | CPU调度的基本单位 |
| 数据共享 | 困难,需要IPC(管道、消息队列等) | 简单,直接读写共享内存 |
| 隔离性/稳定性 | 高,一个进程崩溃不影响他人 | 低,一个线程崩溃可能导致整个进程崩溃 |
| 创建/切换开销 | 大 | 小 |
| 性能 | 占用资源多,并发成本高 | 占用资源少,能实现高效并发 |
| 适用场景 | 需要安全隔离的独立任务(浏览器标签、微服务) | 需要紧密协作、高效通信的任务(GUI、并行计算) |
细化类比说明
为了使概念更通俗易懂,下面用类比来举例
用公司与部门来类比进程和线程,能够覆盖两者的本质并体现“资源共享”的核心差异。
把进程比作一家独立运营的公司,把线程比作公司里的多个部门(如技术部、市场部、财务部)。
- 资源共享:部门共享公司资源,对应线程共享进程资源
- 公司(进程)成立时,会获得独立的 “资源包”:包括办公大楼(独立内存空间)、公司账户(系统资源配额)、营业执照和合作合同(打开的文件 / 网络连接)等。
- 这些资源属于整个公司,所有部门(线程)无需单独申请,可直接共享使用:技术部(线程 1)用办公大楼里的工位写代码,市场部(线程 2)用同一栋楼的会议室谈合作,财务部(线程 3)用公司账户走报销 —— 它们共享 “办公大楼”“公司账户” 这些 “进程级资源”,无需重复创建。
- 但每个部门(线程)也有少量 “私有物品”:比如技术部的开发电脑(线程私有栈)、市场部的客户资料夹(线程私有寄存器),这些仅部门内部使用,不与其他部门共享,对应线程的私有执行上下文。
- 调度:部门直接 “干活”,对应线程直接被 CPU 调度
- 外界(操作系统)不会直接给 “公司” 安排具体任务,而是将工作(CPU 时间片)分配给各个 “部门”:比如让技术部本周完成功能开发(线程 1 执行),让市场部下周参加展会(线程 2 执行)。
- 这就像 CPU 不直接调度进程,而是将时间片分配给进程内的线程 —— 部门(线程)是直接 “执行任务的单位”,公司(进程)是 “提供资源的载体”。
- 独立性:公司间完全独立,部门依赖公司存在
- 不同公司(进程)之间资源完全隔离:A 公司的办公大楼(内存)、账户(资源),B 公司无法随意使用,若要合作需走正式商务流程(对应进程间通信 IPC)。
- 部门(线程)完全依赖公司(进程):若公司倒闭(进程终止),所有部门会随之解散(线程强制终止);但一个部门解散(线程退出),只要其他部门还在,公司(进程)仍能正常运营。
- 开销:新增部门成本低,对应线程创建开销小
- 新开一家公司(创建进程)成本极高:需重新租办公楼、注册执照、开通账户,耗时久、投入大(对应进程创建需分配独立内存、初始化资源,开销高)。
- 公司新增部门(创建线程)成本低:只需在现有办公大楼里腾出工位、招几个人,无需重新申请执照和账户(对应线程创建仅需分配私有栈,复用进程资源,开销低)。
举例
例题
下列关于进程与线程的核心特性,描述正确的是( )
A. 线程拥有独立的内存空间,多个线程间需通过 IPC 机制实现资源共享
B. 进程切换时仅需保存程序计数器和寄存器值,切换开销远低于线程
C. 同一进程内的线程共享该进程的文件句柄和全局变量,无需额外通信机制即可访问。
D. 一个进程只能包含一个线程,若需并发执行多个任务,必须创建多个进程
在 Windows 系统中,用户同时打开 “记事本” 和 “Chrome 浏览器”,此时系统中存在的进程与线程关系是( )
A. 1 个进程,多个线程
B. 2 个进程,每个进程至少 1 个线程。
C. 多个进程,每个进程仅 1 个线程
D. 1 个进程,2 个线程
答案:
C
B
案例
浏览器进程演示与线程阻塞演示
- 任务管理器下查看 chorme 的多个进程
任务管理器(ctrl shift esc)
chorme 有自己的进程分类,但可以简单认为一个标签页会占用一个进程,下面进行操作演示
运行 chorme.exe 启动网页 ,系统进程数增加;为该网页分配了独立的资源空间,并执行多个不同功能线程维持网页运作。
添加新网页,系统进程数增加。
在 chorme 里的任务管理(shift+esc) 处可以查看到具体的进程信息,分为标签页进程、扩展程序进程、浏览器进程
任务管理器中不能直接看到每个进程内的具体线程,但可以通过进程类型大致猜测线程情况。例如,一个渲染进程中可能包含 GUI 渲染线程、JS 引擎线程、事件触发线程等多种线程。
演示点:
1.关闭网页,系统进程数减少;该进程资源销毁,但不影响其他标签页(体现进程独立性)
2.当一个线程崩溃(如 JS 死循环)会导致当前标签页无响应,即被完全阻塞(体现线程依赖进程以及对进程和其他线程的影响)
data:text/html,<button onclick="loop()">BUTTON</button><script>function loop(){while(true){}}</script>
访问后,页面立即卡主,无法右键、滚动等操作。因为 JS 线程是渲染进程的核心线程,负责页面交互,它被阻塞后,整个页面交互失效。体现线程崩溃影响其他线程和该进程。
该任务的 CPU 使用率飙升 (0 - 100),关闭后又接近于 0。体现线程是进程的执行体。
其他标签页不受影响。体现进程间是相互独立的,其他进程不受该进程线程阻塞的影响。
上述演示体现了什么
- 进程是资源隔离的基本单位
- 每个标签页对应独立进程,关闭一个标签页仅销毁其所属进程的资源(内存、CPU 占用),不影响其他标签页进程,直接验证了 “进程间资源完全隔离,一个进程的生命周期变化不干扰其他进程” 的特性。
- 例如:关闭卡死的标签页后,其他标签页仍能正常滚动、点击,正是进程独立性的直观体现。
- 线程是进程内的执行单元,依赖进程存在
- 渲染进程内的 JS 线程(执行代码)、GUI 线程(页面渲染)等分工协作,当 JS 线程因死循环阻塞时,整个渲染进程的核心执行能力失效(页面卡住、无响应),证明 “线程是进程的‘执行手脚’,线程异常会直接拖累所属进程”。
- 同时,JS 线程阻塞导致 CPU 使用率飙升,关闭标签页(销毁进程)后 CPU 占用回落,进一步说明 “线程的执行消耗进程分配的资源,线程是进程资源的实际使用者”。
- 多进程架构平衡 “稳定性” 与 “功能性”
- 单个标签页的线程异常仅影响自身进程,不扩散到浏览器主进程或其他标签页进程,避免了 “一个页面卡死导致整个浏览器崩溃” 的问题,这正是 Chrome 采用多进程架构的核心目的 —— 用进程隔离降低故障影响范围。
基于Python实现的进程与线程资源共享辨析
多进程 vs 多线程的资源共享对比(Python 示例)
核心代码
import os
import threading
import multiprocessing
from time import time
# 全局变量(模拟共享资源)
count = 0 # 父进程的全局变量,这里的父进程指的是运行中的该脚本程序
# 累加函数
def add():
global count # 子进程中声明“全局变量”,但实际是子进程自己的count
for _ in range(1000000):
count += 1
# 多线程演示(共享全局变量)
def test_threads():
global count # 子进程执行此函数时,操作的是自己内存中的count
count = 0 # 父进程初始化count为0
start = time()
t1 = threading.Thread(target=add) # 创建线程1,执行 add
t2 = threading.Thread(target=add) # 创建线程2, 执行 add
t1.start()
t2.start()
t1.join() # 等待 t1 线程执行完毕
t2.join()
end = time()
print(f"多线程结果:{count}") # (理论应为2000000,因无锁可能小于该值,但体现共享)共享了 count
print(f"多线程总共时间:{end-start} s")
# 多进程演示(不共享全局变量)
def test_processes():
global count
count = 0 # 父进程重新初始化 count 为 0
start = time()
p1 = multiprocessing.Process(target=add) # 子进程p1启动,复制父进程内存(count=0),在自己的内存中执行add()
p2 = multiprocessing.Process(target=add) # 子进程p2启动,同样复制父进程内存(count=0),在自己的内存中执行add()
p1.start()
p2.start()
p1.join() # 等待p1执行完毕(p1的count已加到1000000,但这是它自己的内存)
p2.join()
end = time()
print(f"多进程结果:{count}") # 父进程的count始终是0,因为没被修改过
print(f"多进程总共时间:{end-start} s")
if __name__ == "__main__":
test_threads()
test_processes()
# 闭坑点,第一次多线程已经修改了父进程的 count 值,在后续的进程演示中,重新初始化了 count 为 0。
输出
多线程结果:2000000
多线程总共时间:0.23758912086486816 s
多进程结果:0
多进程总共时间:0.25783562660217285 s
图像可视化代码
import os
import time
import threading
import multiprocessing
import matplotlib.pyplot as plt
import matplotlib.animation as animation
# 解决中文显示
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False
# 全局变量定义
data = [] # 存储数据:[相对时间, 角色, count值]
global_start_time = 0 # 全局开始时间
count = 0 # 共享计数变量(仅用于线程)
def log(role, current_count):
"""记录带相对时间的数据"""
relative_time = time.time() - global_start_time
data.append([relative_time, role, current_count])
# 累加函数(区分线程/进程)
def add(role, is_thread=True):
if is_thread:
# 线程:共享全局count
global count # 此处需要声明,因为要修改全局变量count
log(role, count)
for _ in range(10):
count += 1
log(role, count)
time.sleep(0.1)
else:
# 进程:独立局部变量
local_count = 0
log(role, local_count)
for _ in range(10):
local_count += 1
log(role, local_count)
time.sleep(0.1)
# 多线程测试
def test_threads():
global count # 声明要修改全局count
count = 0
log("父-线程", count) # 父线程初始值
t1 = threading.Thread(target=add, args=("t1", True))
t2 = threading.Thread(target=add, args=("t2", True))
t1.start()
t2.start()
t1.join()
t2.join()
log("父-线程", count) # 父线程最终值
time.sleep(0.5) # 间隔区分阶段
# 子进程函数(移到模块级别)
def process_add(role, shared_data, start_time):
local_count = 0
shared_data.append([time.time() - start_time, role, local_count])
for _ in range(10):
local_count += 1
shared_data.append([time.time() - start_time, role, local_count])
time.sleep(0.1)
shared_data.append([time.time() - start_time, role, local_count]) # 结束值
# 多进程测试(使用Manager共享数据)
def test_processes():
global count
count = 0
log("父-进程", count) # 父进程初始值
# 使用Manager创建共享列表
manager = multiprocessing.Manager()
shared_data = manager.list()
p1 = multiprocessing.Process(target=process_add, args=("p1", shared_data, global_start_time))
p2 = multiprocessing.Process(target=process_add, args=("p2", shared_data, global_start_time))
p1.start()
p2.start()
# 等待子进程结束
p1.join()
p2.join()
# 将共享数据添加到全局data中
for item in shared_data:
data.append(item)
# 记录父进程最终值(始终为0)
log("父-进程", count)
time.sleep(0.5) # 间隔区分阶段
# 动态绘图更新函数
def update(frame):
ax.clear()
ax.set_title("线程共享资源 vs 进程内存隔离")
ax.set_xlabel("相对时间(秒)")
ax.set_ylabel("count值")
ax.set_xlim(0, 6)
ax.set_ylim(-1, 22)
ax.grid(alpha=0.3)
current_data = data[:frame]
if not current_data:
return
# 角色样式定义
roles = ["父-线程", "t1", "t2", "父-进程", "p1", "p2"]
colors = ["black", "red", "blue", "gray", "purple", "orange"]
markers = ["o", "s", "^", "o", "x", "+"]
linestyles = ["-", "-", "-", "--", "--", "--"]
# 按角色绘图
for role, color, marker, ls in zip(roles, colors, markers, linestyles):
role_data = [d for d in current_data if d[1] == role]
if role_data:
times = [d[0] for d in role_data]
counts = [d[2] for d in role_data]
ax.plot(times, counts, color=color, marker=marker, label=role,
linestyle=ls, alpha=0.7, markersize=8 if role.startswith("父") else 6)
ax.legend(loc="upper left")
if __name__ == "__main__":
global_start_time = time.time()
data.clear() # 清空数据
# 执行测试
test_threads()
test_processes()
# 创建图表
fig, ax = plt.subplots(figsize=(12, 6))
# 动态绘图
ani = animation.FuncAnimation(
fig,
update,
frames=len(data)+1,
interval=100,
blit=False,
repeat=False
)
plt.tight_layout()
plt.show()
逻辑图示意

可视化运行

注
本文原是为了操作系统课堂演示所制作,供大家参考。(后因意外没用上)
开头参考 从0开始数 的视频



1万+

被折叠的 条评论
为什么被折叠?



