跳到主要内容

进程与线程

· 阅读需 7 分钟
XingHunm
Tech Enthusiast

想象你正在经营一家餐厅,需要同时服务多位客人。这个场景恰好能完美地解释操作系统中的进程和线程。

1. 一个餐厅的故事

1.1 场景一:每个客人一家餐厅(多进程模型)

假设你为每位客人开一家独立的餐厅:

  • 每家餐厅(进程)都有自己的厨房、餐具、食材、服务员
  • 完全隔离:A 餐厅的客人不会影响 B 餐厅的客人
  • 成本高昂:每开一家新餐厅,都要买地、装修、招人、采购设备
  • 沟通困难:两家餐厅的厨师想交流菜谱,得打电话、发邮件

这就是进程的特点:独立、安全,但资源消耗大

1.2 场景二:一家餐厅多个服务员(多线程模型)

现在改成一家大餐厅,有多个服务员:

  • 同一个餐厅(进程)内,多个服务员(线程)同时工作
  • 共享资源:共用一个厨房、仓库、收银台
  • 协作高效:服务员可以直接在厨房窗口交流
  • 相互影响:一个服务员打翻了酱料,可能影响其他服务员的工作
  • 成本低:只需要雇佣新员工,不用再开新餐厅

这就是线程的特点:轻量、共享资源,但需要协调

2. 进程:独立的餐厅

2.1 餐厅的"房间"(内存空间)

每个进程就像一家独立餐厅,有自己的"房间":

🏢 进程餐厅 A 号
├── 📖 菜单(代码段) ← 固定不变的菜谱
├── 🗄️ 仓库(数据段) ← 存放食材和餐具
├── 📦 临时仓库(堆) ← 随时补货的空间
├── 📋 订单板(栈) ← 当前正在处理的订单
└── 📊 管理记录(PCB) ← 餐厅运营状态

2.2 餐厅的"营业状态"(进程状态)

一家餐厅的一天:

🏗️ 装修中(新建)

⏳ 准备营业(就绪) → 等待市场监督局批准

🍳 正在营业(运行) → 厨师正在炒菜

⏸️ 暂停营业(阻塞) → 食材用完了,等待供应商送货

🏁 歇业(终止)

2.3 餐厅间如何沟通?(进程间通信)

既然每家餐厅独立,它们怎么交流呢?

2.3.1 方式一:电话专线(管道 Pipe)

# 餐厅 A 把客流量数据传给餐厅 B 分析
ps aux | grep "node"

2.3.2 方式二:留言板(消息队列)

// 餐厅 A 在公共留言板留言
messageQueue.send({
from: "餐厅A",
message: "今日鱼类特别新鲜",
});

// 餐厅 B 查看留言
messageQueue.receive();

2.3.3 方式三:共享仓库(共享内存)

  • 最快,但需要预约制度(锁机制)
  • 避免两个餐厅同时去仓库拿同一包面粉

2.3.4 方式四:发传单(信号)

# 市场监督局给餐厅发停业信号
kill -9 <餐厅ID>

3. 线程:餐厅里的服务员

3.1 一个服务员的"工具箱"

每个线程(服务员)有自己的:

👤 服务员小李(线程 1)
├── 🆔 工号牌(线程 ID)
├── 📝 记事本(程序计数器) → 记住"我刚做到哪一步"
├── 👐 手里拿的东西(寄存器) → 当前正在处理的菜单
└── 🎒 个人背包(栈) → 存放临时数据

但他们共享餐厅的:

  • 厨房(堆内存)
  • 食材仓库(全局变量)
  • 收银台(文件句柄)

3.2 两种管理方式

3.2.1 方式一:服务员自己协调(用户级线程)

  • 服务员们商量好谁做什么
  • 老板(操作系统)不关心
  • 灵活高效,但一个服务员累倒了,老板都不知道

3.2.2 方式二:老板统一调度(内核级线程)

  • 老板安排每个服务员的工作
  • 可以把服务员派到不同区域(多核 CPU)
  • 调度有开销,但更可靠

4. 形象对比:进程 vs 线程

对比维度进程(独立餐厅)线程(餐厅服务员)
独立性🏢 各自有独立的厨房和仓库👥 共享同一个厨房和仓库
开业成本💰💰💰 需要买地、装修、采购💰 只需雇一个新员工
沟通方式📞 打电话、发邮件💬 直接在厨房窗口喊话
出事影响✅ 一家倒闭不影响其他⚠️ 一人犯错可能影响全餐厅
切换成本🚗 开车去另一家餐厅(慢)🚶 走几步到另一个餐桌(快)

5. 协程:服务员的"分身术"

如果说线程是服务员,协程 (Coroutine) 就是服务员的"分身术"。

5.1 核心概念

协程是用户态的轻量级线程,不由操作系统内核管理,而是由程序自己控制。

  • 极度轻量:占用内存极小(几 KB vs 线程的几 MB)。
  • 完全可控:程序自己决定何时切换(非抢占式)。
  • 无锁优势:同一线程内串行执行,无需复杂的锁机制。

5.2 形象比喻

  • 多线程:雇佣 10 个服务员,每人专门盯着一桌客人。
  • 协程:雇佣 1 个超级服务员,利用客人思考(I/O 等待)的空隙,快速在多桌之间切换服务。
    • 结果:一个人干了 10 个人的活,且省去了服务员交接班的开销。

6. 选择指南:开什么样的餐厅?

6.1 场景一:数据计算工厂(CPU 密集型)

6.1.1 问题

需要处理 1000 万张图片,每张都要压缩、加水印。

6.1.2 选择:多进程

为什么?
📷 图片处理 = 厨师做菜(需要真材实料地计算)
🔥 CPU = 炉灶(只有 4 个炉灶,同时最多炒 4 个菜)

✅ 开 4 家独立餐厅(4 个进程),每家用一个炉灶
❌ 一家餐厅 100 个服务员,但只有 4 个炉灶 → 大家抢着用,效率低

6.1.3 最优配置

  • 进程数 = CPU 核心数(如 4 核 CPU → 4 个进程)

6.2 场景二:客服中心(I/O 密集型)

6.2.1 问题

同时服务 10000 个在线聊天的用户。

6.2.2 选择:事件驱动(Node.js)或协程(Go)

为什么?
💬 聊天 = 客人点餐后等待(大部分时间在等,不占用 CPU)

✅ 协程/事件驱动:一个超级店员,利用等待间隙处理 10000 个客人
❌ 多线程:10000 个服务员挤在一起(内存爆炸,管理混乱)

6.3 场景三:视频剪辑软件(需要共享数据)

6.3.1 问题

多个功能模块需要频繁访问同一段视频数据。

6.3.2 选择:多线程

为什么?
🎬 视频数据 = 厨房的食材仓库

✅ 多个服务员共用一个厨房仓库(多线程共享内存)
❌ 每个服务员开一家餐厅(多进程)→ 每家都要复制一份食材,浪费

7. 性能的秘密

7.1 切换的代价

想象你在做三件事:炒菜、接电话、辅导孩子作业

  • 进程切换 = 🏠 跑到另一个房间

    • 需要带走所有工具和材料
    • 花费时间:微秒级(很慢)
  • 线程切换 = 🚶 走到桌子另一边

    • 只需要换个位置,工具还在
    • 花费时间:纳秒级(快)
  • 协程切换 = 👀 眼神转移

    • 只是注意力转移
    • 花费时间:几乎为 0(超快)

8. 总结:餐厅管理学

概念比喻特点适用场景
进程独立餐厅资源隔离,成本高,安全CPU 密集型 (Chrome 各个 Tab)
线程服务员资源共享,成本低,需同步数据共享型 (视频编辑)
协程分身术极致轻量,用户态调度高并发 I/O (聊天服务、网关)

最佳实践

  • CPU 密集型:进程/线程数 ≈ CPU 核心数
  • I/O 密集型:使用协程或事件驱动
  • 资源复用:使用线程池/连接池
  • 安全第一:多线程读写必须加锁