Skip to content

01-并发基础概念

Python 3.11+

概念铺垫

并发与并行

实际场景

一个 Web 应用需要同时处理 100 个并发请求。如果逐个处理,用户等待时间会非常长。如何让这些请求"同时"进行?

问题:如何理解并发和并行的区别?

并发(Concurrency)并行(Parallelism) 是两个容易混淆的概念。

┌─────────────────────────────────────────────────────────────┐
│              并发 vs 并行                                    │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   并发(Concurrency):                                     │
│   • 多个任务交替执行,看起来同时运行                        │
│   • 单核 CPU 也能实现                                       │
│   • 重点:任务调度,提高响应性                              │
│                                                             │
│   并行(Parallelism):                                      │
│   • 多个任务真正同时执行                                    │
│   • 需要多核 CPU                                            │
│   • 重点:计算能力,提高吞吐量                              │
│                                                             │
│   ┌─────────────────────────────────────────────────────┐   │
│   │  单核 CPU(并发)                                     │   │
│   │  时间 ────────────────────────────────────────────►  │   │
│   │  任务 A: ████────████────████                        │   │
│   │  任务 B: ────████────████────████                    │   │
│   │           (交替执行)                                │   │
│   └─────────────────────────────────────────────────────┘   │
│                                                             │
│   ┌─────────────────────────────────────────────────────┐   │
│   │  多核 CPU(并行)                                     │   │
│   │  核心 1: ████████████████████████████████            │   │
│   │  核心 2: ████████████████████████████████            │   │
│   │           (同时执行)                                │   │
│   └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

生活比喻

┌─────────────────────────────────────────────────────────────┐
│              生活比喻                                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   并发:一个人同时处理多件事                                │
│   • 厨师一边煮汤,一边切菜,一边看火                        │
│   • 实际上是交替做,但看起来同时进行                        │
│                                                             │
│   并行:多个人同时处理多件事                                │
│   • 三个厨师分别煮汤、切菜、看火                            │
│   • 真正同时进行                                            │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Python GIL

什么是 GIL

GIL(Global Interpreter Lock,全局解释器锁)是 CPython 的一个机制,确保同一时刻只有一个线程执行 Python 字节码。

┌─────────────────────────────────────────────────────────────┐
│                    GIL 的作用                                │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   Python 进程                                               │
│   ┌─────────────────────────────────────────────────────┐   │
│   │                                                     │   │
│   │   ┌─────────┐                                       │   │
│   │   │   GIL   │ ← 同一时刻只有一个线程能持有          │   │
│   │   └────┬────┘                                       │   │
│   │        │                                            │   │
│   │   ┌────┴────────────────────────┐                   │   │
│   │   │         线程池              │                   │   │
│   │   │  ┌─────┐ ┌─────┐ ┌─────┐   │                   │   │
│   │   │  │线程1│ │线程2│ │线程3│   │                   │   │
│   │   │  └─────┘ └─────┘ └─────┘   │                   │   │
│   │   └───────────────────────────┘                   │   │
│   │                                                     │   │
│   └─────────────────────────────────────────────────────┘   │
│                                                             │
│   结果:多线程无法利用多核 CPU 进行计算                     │
│                                                             │
└─────────────────────────────────────────────────────────────┘

I/O 密集型 vs CPU 密集型

┌─────────────────────────────────────────────────────────────┐
│              任务类型对比                                    │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   I/O 密集型:                                              │
│   • 大部分时间在等待 I/O                                    │
│   • CPU 空闲,适合多线程                                    │
│   • 示例:网络请求、文件读写、数据库查询                    │
│                                                             │
│   CPU 密集型:                                              │
│   • 大部分时间在计算                                        │
│   • CPU 繁忙,受 GIL 限制                                   │
│   • 示例:数值计算、图像处理、加密解密                      │
│                                                             │
│   ┌─────────────────────────────────────────────────────┐   │
│   │  I/O 密集型任务时间线                                 │   │
│   │  ████████░░░░░░░░░░░░████████░░░░░░░░░░░░████████    │   │
│   │  │ 计算 │  等待 I/O  │ 计算 │  等待 I/O  │ 计算 │    │   │
│   │  └──────┴───────────┴──────┴───────────┴──────┘    │   │
│   │          ↑ 大部分时间在等待                          │   │
│   └─────────────────────────────────────────────────────┘   │
│                                                             │
│   ┌─────────────────────────────────────────────────────┐   │
│   │  CPU 密集型任务时间线                                 │   │
│   │  ████████████████████████████████████████████████    │   │
│   │  │                    计算                        │    │   │
│   │  └────────────────────────────────────────────────┘    │   │
│   │          ↑ 大部分时间在计算                          │   │
│   └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

分层学习

L1 理解层:会用

GIL 的影响 — 代码演示

python
# gil_impact_demo.py
from __future__ import annotations

import threading
import time

def cpu_bound_task(n: int) -> int:
    """CPU 密集型任务"""
    count: int = 0
    for i in range(n):
        count += i
    return count

# 单线程
start: float = time.time()
cpu_bound_task(10_000_000)
cpu_bound_task(10_000_000)
print(f"单线程: {time.time() - start:.2f}s")

# 多线程(受 GIL 限制,不会更快)
start: float = time.time()
t1: threading.Thread = threading.Thread(target=cpu_bound_task, args=(10_000_000,))
t2: threading.Thread = threading.Thread(target=cpu_bound_task, args=(10_000_000,))
t1.start()
t2.start()
t1.join()
t2.join()
print(f"多线程: {time.time() - start:.2f}s")
# 结果:多线程可能更慢(线程切换开销)

GIL 什么时候释放

┌─────────────────────────────────────────────────────────────┐
│              GIL 释放时机                                    │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   1. I/O 操作时                                             │
│      • 文件读写                                             │
│      • 网络请求                                             │
│      • 用户输入                                             │
│      → 多线程对 I/O 密集型任务有效                          │
│                                                             │
│   2. 时间片到期                                             │
│      • Python 3.2+:每 5ms 切换一次                         │
│      • 线程自愿释放 GIL                                     │
│                                                             │
│   3. 执行 C 扩展代码时                                      │
│      • NumPy、Pandas 等库                                   │
│      • 可以绕过 GIL                                         │
│                                                             │
└─────────────────────────────────────────────────────────────┘

选择并发模型

任务类型推荐模型原因
I/O 密集型多线程 / asyncioGIL 在 I/O 时释放,可并发执行
CPU 密集型多进程绕过 GIL,利用多核
混合型多进程 + 多线程根据任务特点组合使用

L2 实践层:用好

推荐做法表

推荐做法说明为什么
先判断任务类型再选并发模型I/O 型用线程/协程,CPU 型用进程错误的模型会导致性能更差
先写串行版本再优化正确性优先于性能并发代码调试困难,串行版本可作为参考基准
time.perf_counter() 测量避免 time.time() 受系统时钟调整影响性能分析需要高精度计时
临界并发数 = CPU 核数CPU 密集型场景超过核数会产生上下文切换开销,反而降低吞吐

反模式对比

❌ 反模式✅ 正确做法说明
混淆并发和并行先判断任务类型:I/O 还是 CPU以为多线程就能加速所有任务
忽略 GIL 影响CPU 密集型用多进程用多线程做 CPU 计算,反而更慢
过早优化先写正确的串行版本,再优化一开始就写并发代码增加复杂度
无限制创建线程使用线程池 ThreadPoolExecutor线程有内存和调度开销,超量反而降低性能

适用场景表

场景是否推荐并发原因
批量下载网页✅ 推荐I/O 密集型,加速明显
批量计算素数❌ 不推荐用线程受 GIL 限制,改用多进程
处理单个小文件❌ 不推荐并发开销 > 收益
Web 服务器响应请求✅ 推荐高并发 I/O 场景

L3 专家层:深入

GIL 的 C 语言实现

GIL 本质上是 CPython 解释器中的一个 互斥锁(mutex),位于 _PyRuntime.gil

┌─────────────────────────────────────────────────────────────┐
│              GIL 内部实现机制                                 │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   CPython 中的 GIL 结构(简化):                            │
│                                                             │
│   ┌──────────────────────────────────────────────────┐      │
│   │  _PyRuntime.gil                                   │      │
│   │  ├── mutex        ← 互斥锁                        │      │
│   │  ├── cond         ← 条件变量(线程等待用)        │      │
│   │  ├── locked       ← 是否被持有                   │      │
│   │  ├── switch_interval ← 切换间隔(默认 5ms)       │      │
│   │  └── last_holder  ← 最后持有者(调试用)          │      │
│   └──────────────────────────────────────────────────┘      │
│                                                             │
│   获取 GIL 流程:                                            │
│   1. 尝试 acquire mutex                                     │
│   2. 如果 locked → wait on cond(挂起)                     │
│   3. 获取成功后 set locked = True                           │
│   4. 执行字节码                                             │
│   5. 达到 switch_interval → release                         │
│                                                             │
└─────────────────────────────────────────────────────────────┘

GIL 释放机制详解

python
# gil_release_demo.py
"""演示 GIL 在 I/O 操作时释放"""
import threading
import time

gil_released: bool = False

def io_bound_task() -> None:
    global gil_released
    with open("/dev/null", "r") as f:
        gil_released = True
        time.sleep(0.1)  # time.sleep 也释放 GIL

def cpu_bound_task() -> None:
    """此任务持有 GIL 直到切换间隔"""
    total: int = 0
    for i in range(10_000_000):
        total += i

t1 = threading.Thread(target=io_bound_task)
t2 = threading.Thread(target=cpu_bound_task)
t1.start()
t2.start()
t1.join()
t2.join()

GIL 释放操作对照表:

操作GIL 状态其他线程能运行吗?
Python 字节码执行持有❌ 不能
time.sleep()释放✅ 能
file.read()释放✅ 能
socket.recv()释放✅ 能
NumPy 矩阵运算释放(C 扩展)✅ 能
python
# gil_check_interval_demo.py
"""
检查 GIL 切换间隔(Python 3.2+ 默认 5ms)

sys.getswitchinterval() 返回线程切换间隔(秒)。
间隔越小,线程切换越频繁,但上下文切换开销也越大。
"""
import sys

print(f"线程切换间隔: {sys.getswitchinterval()}s")  # 默认 0.005 (5ms)

sys.setswitchinterval(0.001)  # 改为 1ms(不建议)

性能考量表

操作单线程耗时多线程耗时说明
CPU 计算 2×10⁷次加法~0.5s~0.7s多线程更慢(GIL+切换开销)
I/O 等待 10×0.5s~5.0s~0.5s多线程加速 10 倍
混合任务~3.0s~1.5s部分加速(I/O 部分释放 GIL)

性能测试代码:

python
# gil_performance_test.py
import time
import threading
from concurrent.futures import ThreadPoolExecutor

def cpu_work() -> int:
    return sum(i * i for i in range(1_000_000))

def io_work() -> None:
    time.sleep(0.5)

# CPU 密集型:多线程更慢
start = time.time()
for _ in range(4):
    cpu_work()
single_cpu = time.time() - start

start = time.time()
with ThreadPoolExecutor(max_workers=4) as executor:
    list(executor.map(cpu_work, range(4)))
multi_cpu = time.time() - start

print(f"CPU 单线程: {single_cpu:.2f}s")
print(f"CPU 多线程: {multi_cpu:.2f}s")
# 预期:multi_cpu >= single_cpu

# I/O 密集型:多线程显著加速
start = time.time()
for _ in range(4):
    io_work()
single_io = time.time() - start

start = time.time()
with ThreadPoolExecutor(max_workers=4) as executor:
    list(executor.map(lambda _: io_work(), range(4)))
multi_io = time.time() - start

print(f"I/O 单线程: {single_io:.2f}s")
print(f"I/O 多线程: {multi_io:.2f}s")
# 预期:multi_io ≈ single_io / 4

Python 3.13+ 无 GIL 模式(前瞻性)

引入版本:Python 3.13(实验性) 启动方式python -X free-threading

Python 3.13 引入了 PEP 703(Making the Global Interpreter Lock Optional in CPython),允许在编译时禁用 GIL。

特性说明
状态实验性,Python 3.13 默认关闭
启用方式编译时配置 --disable-gil
影响内存开销增加 ~10%,单线程性能下降 ~5%
适用场景多线程 CPU 密集型任务可真正并行

设计动机

Python 为什么设计 GIL?

设计选择原因替代方案对比
保留 GIL简化 C 扩展开发,保证线程安全Java 无 GIL,但 C API 更复杂
I/O 时释放让 I/O 密集型任务受益所有 I/O 调用都经过 C 层
不取消 GIL(历史原因)大量 C 扩展依赖 GIL 保证安全3.13 开始探索可选 GIL

知识关联图

并发知识关联:
┌───────────────────┐
│  并发 vs 并行     │ ← 概念基础
└────────┬──────────┘


┌───────────────────┐     ┌───────────────────┐
│      GIL          │────→│  线程调度机制      │
│  (全局解释器锁) │     │  5ms 切换间隔      │
└────────┬──────────┘     └───────────────────┘

    ┌────┴────┐
    ▼         ▼
┌───────┐ ┌───────────┐
│ I/O型 │ │  CPU 型   │
│多线程 │ │  多进程   │
└───┬───┘ └─────┬─────┘
    │           │
    ▼           ▼
┌───────┐ ┌───────────┐
│asyncio│ │进程间通信 │
│协程   │ │Queue/Pipe │
└───────┘ └───────────┘

本章小结

┌─────────────────────────────────────────────────────────────┐
│                      并发基础概念 知识要点                    │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   并发 vs 并行:                                             │
│   ✓ 并发:交替执行,单核也能实现                             │
│   ✓ 并行:同时执行,需要多核                                 │
│                                                             │
│   GIL:                                                      │
│   ✓ CPython 的全局解释器锁                                  │
│   ✓ 同一时刻只有一个线程执行字节码                          │
│   ✓ I/O 操作时释放                                          │
│   ✓ Python 3.13+ 开始实验无 GIL 模式                        │
│                                                             │
│   任务类型:                                                 │
│   ✓ I/O 密集型:等待为主,适合多线程/asyncio                 │
│   ✓ CPU 密集型:计算为主,适合多进程                         │
│                                                             │
│   选择模型:                                                 │
│   ✓ I/O 密集型 → 多线程 / asyncio                           │
│   ✓ CPU 密集型 → 多进程                                     │
│                                                             │
└─────────────────────────────────────────────────────────────┘