一、Python 是如何运行的 — 从源码到执行
解释型语言 vs 编译型语言
- 编译型(如 C/C++):源码 → 一次性编译为机器码 → 直接运行。
- 解释型(如 Python/JS):源码 → 边解释边执行,但 Python 实际是“先编译成字节码,再解释执行”。
Python 的执行流程
|
|
关键点:
- 字节码:Python 源码的中间表示,平台无关。
- PVM:不是独立程序,而是 CPython 内部的一个大循环,负责逐条解释执行字节码。
- .pyc 文件:缓存字节码,避免重复编译,提升启动速度。
二、CPython 与 GIL - 为什么多线程不能并行
什么是 GIL
- GIL(Global Interpreter Lock) 是 CPython 解释器内部的一个互斥锁。
- 作用:确保同一时刻只有一个线程在执行 Python 字节码。
- 目的:保护 CPython 的引用计数内存管理机制,避免多线程并发修改对象导致崩溃。
注意:
- GIL 是 CPython 特有(PyPy/Jython 无 GIL)。
- GIL 锁的是 “执行字节码”,不是整个程序。
GIL 如何工作
- 线程要执行 Python 字节码 → 必须先获取 GIL。
- 遇到以下情况会释放 GIL:
- I/O 操作(如 requests.get, time.sleep, 文件读写)
- 执行 C 扩展(如 NumPy、Pillow)
- 每执行一定字节码指令后主动让出(时间片切换)
三、并发模型:进程/线程/协程
层级关系
|
|
包含关系:协程 属于 线程 属于 进程 属于 操作系统
一个 Python 程序的默认结构
- 运行 python main.py → 启动 1 个进程 + 1 个主线程。
- 在主线程中,可创建:
- 多个线程(threading.Thread)→ 但受 GIL 限制,不能并行执行 Python 代码
- 多个协程(async def + await)→ 在单线程内并发,无 GIL 争抢
多进程/多线程/协程
| 方式 | 是否绕过 GIL | 适用场景 | 并发/并行 | 开销 |
|---|---|---|---|---|
| 多进程 | 是 | CPU 密集型 | 真正并行 | 高(进程切换) |
| 多线程 | 否 | I/O 密集型 | 并发 | 中(GIL 切换) |
| 协程 | 单线程) | I/O 密集型 | 并发 | 极低 |
实践建议:
- Web 后端(FastAPI/aiohttp)→ 用 协程(async/await)
- 科学计算/图像处理 → 用 多进程(multiprocessing)
- 调用老式阻塞库 → 用 线程池(如 asyncio.to_thread)
四、同步/异步/阻塞/非阻塞
同步Synchronous/异步Asynchronous
-
data = requests.get(url) # 阻塞直到返回
-
data = await httpx.get(url) # 不阻塞,可去做别的事
异步的核心:不无脑等待阻塞,提高资源利用率。
阻塞Blocking/非阻塞Non-blocking
- 阻塞:调用后必须等待操作完成才返回(如默认的 socket.recv())。
- 非阻塞:调用立即返回,无论是否准备好(如设置 socket.setblocking(False))。
关键联系: - 异步编程 依赖非阻塞 I/O。 - 同步编程 通常使用阻塞 I/O。
五、总结
- Python 程序运行 = 源码 → 字节码 → PVM 执行
- CPython 用 GIL 保护字节码执行,导致多线程不能并行 Python 代码
- 一个 python xxx.py = 1 进程 + 1 主线程(可有多个协程)
- 多进程 = 真正并行(绕过 GIL),适合 CPU 密集
- 协程 = 单线程内高效并发,适合 I/O 密集(如 FastAPI)
- 异步 = 非阻塞 + 事件循环,最大化 I/O 利用率
目录