← 返回 Day 54 正文

Day 54 · 图解

Day 054 — 并发入门:线程 — 图解

1. GIL(全局解释器锁)工作原理

时间线 ──────────────────────────────────────────────────────▶

CPU 核心(单核执行 Python 字节码):
┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐
│ 线程 A   │  │ 线程 B   │  │ 线程 A   │  │ 线程 B   │
│ 执行字节码│  │ 执行字节码│  │ 执行字节码│  │ 执行字节码│
│ (持有GIL)│  │ (持有GIL)│  │ (持有GIL)│  │ (持有GIL)│
└────┬─────┘  └────┬─────┘  └────┬─────┘  └────┬─────┘
     │              │              │              │
     ▼              ▼              ▼              ▼
  I/O 阻塞      I/O 阻塞      I/O 阻塞      I/O 阻塞
  释放 GIL      释放 GIL      释放 GIL      释放 GIL

  💡 I/O 时释放 GIL → 其他线程获得执行机会
  💡 CPU 密集时持有 GIL → 多线程无法并行

2. 线程安全 vs 竞态条件

❌ 没有锁的情况(竞态条件):

  线程 A                线程 B
  ──────                ──────
  读取 counter (=5)     
  计算 counter+1 (=6)   
                         读取 counter (=5)  ← 还是旧值!
                         计算 counter+1 (=6)
  写入 counter (=6)
                         写入 counter (=6)  ← 丢失了线程A的更新!

  最终结果: 6(期望 7)

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

✅ 使用锁的情况:

  线程 A                线程 B
  ──────                ──────
  获取 Lock ✓           等待 Lock...
  读取 counter (=5)     
  计算 counter+1 (=6)   
  写入 counter (=6)     等待 Lock...
  释放 Lock ✗           
                         获取 Lock ✓
                         读取 counter (=6)  ← 正确的新值!
                         计算 counter+1 (=7)
                         写入 counter (=7)
                         释放 Lock ✗

  最终结果: 7 ✓

3. 生产者-消费者模型

  生产者线程                 缓冲区                 消费者线程
  ┌──────────┐          ┌──────────┐          ┌──────────┐
  │ 生产数据  │ ──────▶  │          │  ──────▶  │ 消费数据  │
  │          │          │ [A][B][C]│          │          │
  │          │  notify  │          │  notify   │          │
  └────┬─────┘          └──────────┘          └────┬─────┘
       │                                           │
       │          ┌──────────┐                     │
       └───────── │ Condition│ ◀──────────────────┘
                  │  wait()  │
                  └──────────┘

  流程:
  1. 生产者生产数据,放入缓冲区
  2. 生产者调用 notify() 唤醒消费者
  3. 消费者从缓冲区取出数据
  4. 消费者调用 notify() 唤醒生产者
  5. 如果缓冲区满,生产者 wait()
  6. 如果缓冲区空,消费者 wait()

4. 并发下载器架构

  ┌─────────────────────────────────────────────┐
  │          ThreadPoolExecutor                  │
  │  ┌─────┐  ┌─────┐  ┌─────┐  ┌─────┐       │
  │  │ T1  │  │ T2  │  │ T3  │  │ T4  │       │
  │  └──┬──┘  └──┬──┘  └──┬──┘  └──┬──┘       │
  │     │        │        │        │            │
  │     ▼        ▼        ▼        ▼            │
  │  ┌──────┐┌──────┐┌──────┐┌──────┐         │
  │  │URL 1 ││URL 2 ││URL 3 ││URL 4 │         │
  │  └──────┘└──────┘└──────┘└──────┘         │
  │     │        │        │        │            │
  │     ▼        ▼        ▼        ▼            │
  │  ┌──────────────────────────────────┐       │
  │  │     Results (线程安全收集)         │       │
  │  │  ┌────┐ ┌────┐ ┌────┐ ┌────┐    │       │
  │  │  │ R1 │ │ R2 │ │ R3 │ │ R4 │    │       │
  │  │  └────┘ └────┘ └────┘ └────┘    │       │
  │  └──────────────────────────────────┘       │
  └─────────────────────────────────────────────┘

5. 同步原语对比

┌──────────────┬──────────────────────────────────────┐
│   Lock        │  🔒 互斥锁                           │
│               │  同一时刻只有一个线程能获取             │
│               │  用途: 保护共享变量                    │
├──────────────┼──────────────────────────────────────┤
│   RLock       │  🔒🔒 可重入锁                        │
│               │  同一线程可以多次获取                   │
│               │  用途: 递归场景                       │
├──────────────┼──────────────────────────────────────┤
│   Condition   │  📢 条件变量                         │
│               │  wait() 等待 + notify() 通知          │
│               │  用途: 生产者-消费者                   │
├──────────────┼──────────────────────────────────────┤
│   Semaphore   │  🚦 信号量                           │
│               │  限制同时访问的线程数量                 │
│               │  用途: 资源池、限流                    │
├──────────────┼──────────────────────────────────────┤
│   Event       │  🚩 事件信号                         │
│               │  一次性通知: set() → wait()            │
│               │  用途: 线程间同步启动                  │
└──────────────┴──────────────────────────────────────┘