Python多线程系统学习路线第216讲_核心原理与实战案例详解【指导】

Python多线程无法加速CPU密集型任务,因CPython的GIL强制单线程执行字节码;仅I/O密集型任务适用threading,CPU密集型必须用multiprocessing或ProcessPoolExecutor。

Python 的多线程在 CPU 密集型任务中基本不加速,这是由 GIL(全局解释器锁)决定的,不是写法或版本问题。

为什么 threading 无法提升 CPU 密集型任务性能

CPython 解释器为保证内存管理安全,在同一时刻只允许一个线程执行 Python 字节码。GIL 是互斥锁,不是可配置开关,也无法通过多核绕过。

  • 所有纯 Python 循环、数值计算(如 sum([x**2 for x in range(10**7)]))都受 GIL 限制
  • threading.Thread 启动再多,CPU 使用率也不会超过单核 100%
  • 只有当线程因 I/O(如 time.sleep()requests.get()、文件读写)主动释放 GIL 时,其他线程才能运行

什么时候该用 threading,而不是 multiprocessing

适用场景非常明确:高并发 I/O 等待,且任务间无强状态共享需求。

  • 同时发起 100 个 HTTP 请求 → 用 threading + requests(每个请求阻塞时自动让出 GIL
  • 监听多个 socket 连接并响应 → threadingmultiprocessing 启动快、内存开销小
  • 需要共享内存对象(如 dictlist)且不涉及复杂同步 → threading 可直接引用,multiprocessing 需用 ManagerQueue
  • 避免 multiprocessing 在 Windows 上反复导入主模块的问题

threading.Thread 启动后不执行?常见卡点

最常被忽略的是没调用 start(),而是误调了 run() —— 后者只是普通函数调用,仍在主线程同步执行。

import threading
import time

def worker(): time.sleep(1) print("done")

t = threading.Thread(target=worker) t.run() # ❌ 错误:同步执行,不启新线程

t.start() # ✅ 正确:异步启动线程

  • 忘记 t.join() 导致主线程退出,子线程被强制终止(尤其脚本末尾无等待)
  • 使用 lambda 传参时闭包陷阱:for i in range(3): Thread(target=lambda: print(i)) 会全打印 2,应写成 lambda i=i: print(i)
  • 未处理异常:线程内抛出的异常不会传播到主线程,需在 target 函数里捕获或重写 run()

真正需要并行计算时,绕不开 multiprocessingconcurrent.futures.ProcessPoolExecutor

只要任务是 CPU 密集型(如图像处理、加密、科学计算),必须用进程而非线程。注意:

  • multiprocessing 中的函数必须能被序列化(不能是嵌套函数、lambda、类实例方法,除非用 functools.partial 包装)
  • 进程间通信比线程慢得多,频繁传大数据(如大数组)会成为瓶颈;此时应考虑 mmapshared_memory(Python 3.8+)
  • concurrent.futures.ProcessPoolExecutor 比裸用 multiprocessing.Process 更简洁,推荐作为默认选择

多线程的“并发”和多进程的“并行”,底层机制完全不同;混淆这两者,是绝大多数性能问题的根源。