Python threading 因 GIL 无法并行执行 CPU 密集任务,需改用 multiprocessing;Pool 必须在 if name == '__main__': 块中创建,apply_async 需设 timeout 并捕获异常,传参过大应避免序列化开销。

为什么不能用 threading 处理 CPU 密集型任务
Python 的 threading 模块受 GIL 限制,多个线程在 CPU 密集场景下无法真正并行——它们会轮流抢同一个解释器锁,实际是串行执行,甚至因上下文切换更慢。你看到的“多线程跑得慢”,大概率就是这个原因。
必须换用 multiprocessing:每个进程有独立 Python 解释器和内存空间,GIL 不再构成瓶颈。
multiprocessing.Pool 的正确初始化时机
进程池不能在模块顶层(即 import 级别)直接创建,尤其在 Windows 或使用 spawn 启动方式时,会导致子进程重复执行初始化代码、无限递归 fork,报错如:AssertionError: can only join a started process 或直接卡死。
必须把 Pool 创建放在 if __name__ == '__main__': 保护块内,并确保主模块可被安全导入:
立即学习“Python免费学习笔记(深入)”;
if __name__ == '__main__':
with multiprocessing.Pool(processes=4) as pool:
results = pool.map(my_cpu_func, data_list)
print(results)
注意:pool.map 是阻塞调用;若需真正异步,用 pool.apply_async 并手动 .get()。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
apply_async 返回值与错误捕获的坑
apply_async 立即返回 AsyncResult 对象,不等任务完成。但它的 .get() 方法会**同步阻塞**,且默认无限等待——如果某个子进程崩溃或死循环,主进程就卡住不动。
建议始终加超时,并捕获 TimeoutError 和子进程抛出的原始异常:
-
.get(timeout=30)显式设超时(单位秒) - 子进程里未捕获的异常,会在主进程调用
.get()时原样抛出,不是subprocess.CalledProcessError那种包装过的错误 - 不要在回调函数(
callback/error_callback)里做耗时操作,它运行在主进程,可能拖慢整个池的调度
传参过大时的性能警告
进程间通信靠序列化(pickle),如果传给 pool.map 或 apply_async 的参数是巨型列表、DataFrame 或模型对象,每次任务分发都会触发完整拷贝 + 序列化/反序列化,开销可能远超计算本身。
优化方向:
- 把大对象提前放到全局变量中(仅限 Unix
fork方式,Windows 不适用) - 改用
multiprocessing.Manager共享数据,但要注意锁和性能折损 - 更稳妥的做法:把数据路径或 ID 传进去,让子进程自己加载,避免跨进程搬运
最常被忽略的一点:进程池不是万能加速器。启动进程、序列化、IPC 调度都有固定成本。单次任务小于 100ms 时,往往不如直接 for 循环快。

















