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() │
│ │ 用途: 线程间同步启动 │
└──────────────┴──────────────────────────────────────┘