进程与线程的区别辨析

引入

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

img

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

一个程序若重复使用,则会多次被读取到内存中并运行时,成为多个独立的进程。

在这里插入图片描述

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

在这里插入图片描述
进一步形象化进程与线程概念
想象一家餐厅的后厨就是一个计算机系统。

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

在这里插入图片描述

基本定义

下面归纳一下进程与线程的定义

进程是操作系统资源分配的基本单位。它是程序的一次执行过程,拥有独立的内存空间。一个进程可以包含多个线程,它们共享进程的资源,但是每个线程有自己的执行路径。

线程是进程中的一个执行任务,是处理器任务调度和执行的基本单位。它是比进程更小的能独立运行的基本单位。线程共享其所属进程的地址空间和资源,但拥有自己的程序计数器、虚拟机栈和本地方法栈。线程之间的切换开销小于进程,因此被称为轻量级进程。

关键词说明

执行与调度相关概念

地址空间概念: 操作系统为每个进程分配的、独立的、受保护的虚拟内存范围。可以理解为一个进程所能使用的所有内存地址的集合。为何重要: 这是进程间隔离的基石。进程A不能直接访问进程B的地址空间,从而保证了安全性和稳定性。线程则共享其所属进程的同一个地址空间。

资源概念: 程序运行时所需的各类实体,主要包括:内存: 存放代码和数据。I/O设备: 如打开的文件、网络连接。CPU时间: 被调度执行的权利。为何重要: 进程是资源分配的基本单位。系统为进程整体分配资源(如内存、文件句柄)。线程作为进程的一部分,共享这些已分配的资源。

资源与隔离相关概念

调度: 操作系统的“调度程序”决定接下来该轮到哪个任务(进程或线程)使用CPU。这是一个“选择”的过程,基于优先级、等待时间等算法。

分派: 调度做出选择后,“分派程序”负责具体的切换动作,比如保存当前任务的上下文、加载下一个任务的上下文,然后让CPU开始执行它。为何重要: 线程是CPU调度和分派的基本单位。操作系统直接看到并调度的是线程,而不是整个进程。这就解释了为什么线程切换开销更小(要保存和恢复的上下文信息更少)。

上下文切换概念: 将CPU从一个任务(进程/线程)切换到另一个任务时,必须保存当前任务的状态(上下文),并加载下一个任务的状态的过程。上下文包括寄存器值、程序计数器等。为何重要: 进程的上下文切换涉及内存地址空间的切换(如更换页表),开销巨大。线程的上下文切换发生在同一地址空间内,开销要小得多。 这是线程“轻量”的关键原因

xmind 导图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

对比小结

特性进程线程
基本单位资源分配的基本单位CPU调度的基本单位
数据共享困难,需要IPC(管道、消息队列等)简单,直接读写共享内存
隔离性/稳定性高,一个进程崩溃不影响他人低,一个线程崩溃可能导致整个进程崩溃
创建/切换开销
性能占用资源多,并发成本高占用资源少,能实现高效并发
适用场景需要安全隔离的独立任务(浏览器标签、微服务)需要紧密协作、高效通信的任务(GUI、并行计算)

细化类比说明

为了使概念更通俗易懂,下面用类比来举例

用公司与部门来类比进程和线程,能够覆盖两者的本质并体现“资源共享”的核心差异。

进程比作一家独立运营的公司,把线程比作公司里的多个部门(如技术部、市场部、财务部)。

  1. 资源共享:部门共享公司资源,对应线程共享进程资源
  • 公司(进程)成立时,会获得独立的 “资源包”:包括办公大楼(独立内存空间)、公司账户(系统资源配额)、营业执照和合作合同(打开的文件 / 网络连接)等。
  • 这些资源属于整个公司,所有部门(线程)无需单独申请,可直接共享使用:技术部(线程 1)用办公大楼里的工位写代码,市场部(线程 2)用同一栋楼的会议室谈合作,财务部(线程 3)用公司账户走报销 —— 它们共享 “办公大楼”“公司账户” 这些 “进程级资源”,无需重复创建。
  • 但每个部门(线程)也有少量 “私有物品”:比如技术部的开发电脑(线程私有栈)、市场部的客户资料夹(线程私有寄存器),这些仅部门内部使用,不与其他部门共享,对应线程的私有执行上下文。
  1. 调度:部门直接 “干活”,对应线程直接被 CPU 调度
  • 外界(操作系统)不会直接给 “公司” 安排具体任务,而是将工作(CPU 时间片)分配给各个 “部门”:比如让技术部本周完成功能开发(线程 1 执行),让市场部下周参加展会(线程 2 执行)。
  • 这就像 CPU 不直接调度进程,而是将时间片分配给进程内的线程 —— 部门(线程)是直接 “执行任务的单位”,公司(进程)是 “提供资源的载体”。
  1. 独立性:公司间完全独立,部门依赖公司存在
  • 不同公司(进程)之间资源完全隔离:A 公司的办公大楼(内存)、账户(资源),B 公司无法随意使用,若要合作需走正式商务流程(对应进程间通信 IPC)。
  • 部门(线程)完全依赖公司(进程):若公司倒闭(进程终止),所有部门会随之解散(线程强制终止);但一个部门解散(线程退出),只要其他部门还在,公司(进程)仍能正常运营。
  1. 开销:新增部门成本低,对应线程创建开销小
  • 新开一家公司(创建进程)成本极高:需重新租办公楼、注册执照、开通账户,耗时久、投入大(对应进程创建需分配独立内存、初始化资源,开销高)。
  • 公司新增部门(创建线程)成本低:只需在现有办公大楼里腾出工位、招几个人,无需重新申请执照和账户(对应线程创建仅需分配私有栈,复用进程资源,开销低)。

举例

例题

下列关于进程与线程的核心特性,描述正确的是( )

A. 线程拥有独立的内存空间,多个线程间需通过 IPC 机制实现资源共享

B. 进程切换时仅需保存程序计数器和寄存器值,切换开销远低于线程

C. 同一进程内的线程共享该进程的文件句柄和全局变量,无需额外通信机制即可访问。

D. 一个进程只能包含一个线程,若需并发执行多个任务,必须创建多个进程

在 Windows 系统中,用户同时打开 “记事本” 和 “Chrome 浏览器”,此时系统中存在的进程与线程关系是( )

A. 1 个进程,多个线程

B. 2 个进程,每个进程至少 1 个线程。

C. 多个进程,每个进程仅 1 个线程

D. 1 个进程,2 个线程

答案:

C

B

案例

浏览器进程演示与线程阻塞演示

  1. 任务管理器下查看 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。体现线程是进程的执行体。

其他标签页不受影响。体现进程间是相互独立的,其他进程不受该进程线程阻塞的影响。

上述演示体现了什么

  1. 进程是资源隔离的基本单位
    • 每个标签页对应独立进程,关闭一个标签页仅销毁其所属进程的资源(内存、CPU 占用),不影响其他标签页进程,直接验证了 “进程间资源完全隔离,一个进程的生命周期变化不干扰其他进程” 的特性。
    • 例如:关闭卡死的标签页后,其他标签页仍能正常滚动、点击,正是进程独立性的直观体现。
  2. 线程是进程内的执行单元,依赖进程存在
    • 渲染进程内的 JS 线程(执行代码)、GUI 线程(页面渲染)等分工协作,当 JS 线程因死循环阻塞时,整个渲染进程的核心执行能力失效(页面卡住、无响应),证明 “线程是进程的‘执行手脚’,线程异常会直接拖累所属进程”。
    • 同时,JS 线程阻塞导致 CPU 使用率飙升,关闭标签页(销毁进程)后 CPU 占用回落,进一步说明 “线程的执行消耗进程分配的资源,线程是进程资源的实际使用者”。
  3. 多进程架构平衡 “稳定性” 与 “功能性”
    • 单个标签页的线程异常仅影响自身进程,不扩散到浏览器主进程或其他标签页进程,避免了 “一个页面卡死导致整个浏览器崩溃” 的问题,这正是 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开始数 的视频

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值