并发编程:线程与进程

一、核心比喻

  • 进程 = 工厂(独立资源,稳但重)
  • 线程 = 工人(共享资源,快但易冲突)
  • 一个进程可含多个线程

三种方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
方案 A:一个工厂,一个工人
工厂(进程)
└── 工人(线程)→ 慢

方案 B:一个工厂,多个工人
工厂(进程)
├── 工人1
├── 工人2
└── 工人3
共用厂房、机器、仓库

方案 C:多个工厂,各干各的
工厂1(进程)→ 做上衣
工厂2(进程)→ 做裤子
工厂3(进程)→ 做帽子

二、进程是什么

进程 = 一个正在运行的程序。

  • 独立:每个进程有自己的内存,互不干扰
  • 安全:一个崩了,一般不影响别的
  • 重量级:创建、切换开销大
  • 通信麻烦:要走 IPC(管道、socket 等)

类比:每个工厂是独立的院子,围墙很高,想递东西得走大门。


三、线程是什么

线程 = 进程内部的一个执行流。

一个浏览器进程:

1
2
3
4
5
浏览器进程
├── 线程1:渲染页面
├── 线程2:下载文件
├── 线程3:处理用户点击
└── 线程4:播放视频
  • 共享:同一进程的线程共用内存
  • 轻量:创建、切换开销小
  • 通信快:直接读写共享内存
  • 危险:一个线程崩了可能拖垮整个进程

类比:同一工厂的工人,共用厂房,交流方便,但一个闯祸全厂遭殃。


四、进程 vs 线程

对比项 进程 线程
是什么 运行中的程序 进程里的执行流
资源 独立 共享
内存 各自独立 共用
开销 大(重) 小(轻)
通信 麻烦(IPC) 简单(共享变量)
安全 一个崩了不影响别的 一个崩了可能全挂
数量级 几十 几百上千
比喻 工厂 工人

五、联系

包含关系:程序 > 进程 > 线程

  • 没有进程,线程无处安身
  • 一个进程至少有一个线程(主线程)
  • 线程不能脱离进程单独存在

六、为什么用?

  • 用进程:为了隔离和稳定(Chrome 每标签一进程)
  • 用线程:为了并发和省资源(下载时还能点按钮)

做饭类比

场景 对应
一个厨房一个厨师 单进程单线程
一个厨房多个厨师 单进程多线程
多个厨房各做各的 多进程

七、并发问题:竞态条件

1
2
3
4
count = 0
# 线程1: count = count + 1
# 线程2: count = count + 1
# 理想=2,实际可能=1(两个线程同时读到0,各自加1写回,覆盖)

这叫”竞态条件”,需要加锁(Lock)解决。

类比:两个厨师同时往同一个碗里加盐,都以为自己是第一个。

取舍

  • 进程:隔离好、通信难
  • 线程:共享好、易冲突

八、Python 的 GIL

  • 同一时刻,一个进程里只有一个线程能执行 Python 代码
  • 多线程不能真正并行跑 CPU 密集任务
  • 但适合 I/O 密集任务(等的时候可以切换)

选择

任务类型 选什么 例子
I/O 密集 多线程 下载、读写文件、请求接口
CPU 密集 多进程 计算、图像处理

口诀

“等”的任务用线程,”算”的任务用进程。


九、实战场景

  • 爬虫:多线程/多进程并发抓取,快 N 倍
  • Web 服务:每请求一线程/协程,同时服务很多人
  • 数据清洗:多进程并行处理大文件
  • 图像处理:多进程绕开 GIL
  • 实时系统:注意线程竞争,加锁