
当使用 multiprocessing 并行执行 cpu 密集型 numpy 任务时,增加进程数反而导致单个进程耗时剧增,主因是 numpy(通过 blas)默认启用多线程,引发进程级与线程级的嵌套并行冲突,造成严重资源争抢和上下文切换开销。
当使用 multiprocessing 并行执行 cpu 密集型 numpy 任务时,增加进程数反而导致单个进程耗时剧增,主因是 numpy(通过 blas)默认启用多线程,引发进程级与线程级的嵌套并行冲突,造成严重资源争抢和上下文切换开销。
在您的示例中,每个子进程调用 np.linalg.inv() 时,底层 BLAS 实现(如 OpenBLAS 或 Intel MKL)会自动启动多个线程(通常等于物理核心数)来加速矩阵运算。当您启动 4 个 multiprocessing 进程时,若每个进程又各自启用 8 个 BLAS 线程,则系统将同时运行多达 32 个计算线程——远超 CPU 物理核心数。这不仅无法提升性能,反而因频繁的线程调度、缓存抖动(cache thrashing)、内存带宽饱和及 NUMA 跨节点访问等问题,显著拉低整体吞吐量,甚至使单任务耗时翻倍增长。
验证该问题最直接的方式是监控系统负载:运行脚本时使用 htop 或 uptime 查看 1 分钟平均负载(load average)。若负载值远高于您设置的 num_cpus(例如 4 进程下负载达 20+),即表明存在严重的线程级过度并行。
✅ 正确解法是禁用 NumPy 的内部线程并行,确保“1 进程 = 1 线程”,让 multiprocessing 完全掌控并行粒度。推荐两种稳定可控的方式:
方案一:使用 threadpoolctl(推荐 ✅)
threadpoolctl 可在运行时动态限制 BLAS 线程数,且作用域精准、跨平台兼容。关键点:必须在子进程中调用(pool_func 内),而非主进程。
立即学习“Python免费学习笔记(深入)”;
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
from threadpoolctl import threadpool_limits
import numpy as np
import time
def pool_func(number: int, max_number: int) -> dict:
# ✅ 在每个子进程内立即限制 BLAS 线程为 1
with threadpool_limits(limits=1, user_api='blas'):
pid = str(multiprocessing.current_process().pid)
print(f'[{number:2d}/{max_number:2d} {pid}] Starting ...')
t0 = time.time()
n = 1000
for _ in range(50):
u = np.random.randn(n, n)
v = np.linalg.inv(u) # 此处 now runs single-threaded
t1 = time.time()
print(f'[{number:2d}/{max_number:2d} {pid}] Finished in {t1 - t0:.1f}s.')
return {}⚠️ 注意:
user_api='blas'针对矩阵运算(linalg.inv,@,np.dot等);若涉及 FFT 或其他后端,可扩展为user_api=['blas', 'lapack', 'fft']。
方案二:预设环境变量(备选 ⚠️)
在 if __name__ == "__main__": 块顶部设置环境变量,并在子进程启动前完成(需配合 spawn 上下文):
import os
os.environ["OMP_NUM_THREADS"] = "1"
os.environ["OPENBLAS_NUM_THREADS"] = "1"
os.environ["MKL_NUM_THREADS"] = "1"
os.environ["VECLIB_MAXIMUM_THREADS"] = "1" # macOS Accelerate
os.environ["NUMEXPR_NUM_THREADS"] = "1"
if __name__ == "__main__":
# ... 其余代码保持不变
with multiprocessing.get_context("spawn").Pool(processes=num_cpus) as pool:
results = pool.starmap(pool_func, runs)❗ 重要限制:此方式要求
os.environ设置早于任何import numpy。若您的模块在顶层已导入 NumPy,该设置将失效。因此threadpool_limits更鲁棒。
补充建议
- 始终使用
"spawn"启动方法(如您代码所示),避免"fork"在 NumPy + 多线程场景下的内存不一致风险; - 在 Linux 服务器上,检查是否启用了
turbo boost或CPU frequency scaling,临时关闭可提升性能稳定性(sudo cpupower frequency-set -g performance); - 对于纯计算任务,
concurrent.futures.ProcessPoolExecutor与multiprocessing.Pool行为一致,可按需选用。
通过消除嵌套并行,您将观察到接近线性的加速比:4 进程总耗时 ≈ 单进程耗时,各子进程独立、稳定、高效地利用专属核心资源。

















