Python多进程池运行随时间变慢的排查与解决请求
问题描述
我们尝试通过多进程池运行多个模拟任务以缩短总运行时间,替代串行执行。运行初期CPU和GPU利用率高达95-100%,但随时间推移降至2-4%。
核心代码
import multiprocessing import main_mp def run_sim(process_num, input_list, gpu_device_list): """ multiprocess target function :param input_list: list of input files tasks :param process_num: process number :param gpu_device_list: gpu_device ids for each task """ start_time = time.time() try: print(f"Process number: {process_num} starting") main_mp.run_mp_simulation_flow_dist(input_list, gpu_device_list) status = "completed" except Exception as e: print(f"Process number: {process_num} failed") status = "failed" finally: end_time = time.time() elapsed_time = end_time - start_time log_process_status(process_num, status, elapsed_time) if __name__ == '__main__': # multi-process parameters multi_processes_number = int(multiprocessing.cpu_count()) gpu_workers = get_cuda_device() # get file list and split it to tasks tuple_file_list = get_files(path) data_list, worker_num = split_data(tuple_file_list, args.process_num) # assign gpus to each task to balance the overall workload data_list_sorted = sb.run_sort(data_list, len(gpu_workers)) total_runtime = 0 total_start_time = time.time() multiprocessing.freeze_support() # run simulation flow with multiprocessing.Pool(worker_num) as pool: run_times.append(pool.starmap(run_sim,[(i, data_list_sorted[i][0], data_list_sorted[i][1]) for i in range(worker_num)])) # close all worker processes pool.close() pool.join() total_end_time = time.time() total_runtime = total_end_time - total_start_time save_log(in_file_list, multi_processes_number, gpu_workers) print("multi-process flow completed in ", round(total_runtime/60, 3), " minutes")
环境详情
- Python 3.12.4
- AWS g6.24xlarge实例(96 vCPU、4块NVIDIA L4 GPU)
- CUDA 12.3
- Ubuntu Server 24.04
任务特性
模拟任务由run_mp_simulation_flow_dist()调用,该函数基于OpenMP和CUDA实现,但无法控制其资源分配逻辑。每个进程的模拟任务包含多个串行子任务,子任务完成后会写入磁盘,且子任务通过OpenMP和CUDA加速。
已尝试的调试操作
- 将进程数从96改为40
- 在Ubuntu机器上设置
set_start_method为spawn - 设置
max_tasks_per_child为1 - 相同硬件的Windows Server 2022机器上运行无性能下降现象
进程状态观察
执行ps afx命令显示进程带有--multiprocessing-fork参数,性能下降时多数进程处于R1状态,磁盘写入减少。
疑问
- 是否是进程挂起占用资源导致连锁反应?
- Ubuntu与Windows的核心差异是什么?
- 如何解决该性能下降问题?
解决方案分析
1. 进程挂起与连锁反应的可能性
是,大概率存在进程挂起或阻塞引发的连锁影响。当部分进程因资源竞争、锁等待或磁盘IO阻塞时,会占用CPU调度资源但无法有效执行,导致其他进程的调度优先级被抢占,整体利用率暴跌。R1状态(可运行但等待CPU)说明进程处于就绪队列但得不到调度,结合磁盘写入减少,可能是子任务完成后的磁盘IO操作出现阻塞,或者OpenMP/CUDA的资源冲突导致进程陷入等待。
2. Ubuntu与Windows的核心差异
- 进程启动方式:Ubuntu默认用
fork创建子进程,会复制父进程的内存空间,包括未清理的CUDA上下文、OpenMP线程池状态,容易引发资源冲突;Windows只能用spawn,会重新初始化Python解释器和CUDA环境,资源隔离更彻底。 - 调度器与IO模型:Linux的CFS调度器在多进程密集场景下,若进程频繁切换或出现IO阻塞,调度延迟会显著增加;Windows的IOCP模型对磁盘异步IO的处理更高效,减少进程因等待IO而被挂起的概率。
- CUDA/OpenMP的资源管理:Linux下CUDA上下文默认绑定到进程,fork后的子进程可能共享未正确初始化的CUDA资源,导致内核态等待;Windows下每个进程的CUDA上下文独立,冲突概率更低。
3. 具体解决步骤
- 强制使用spawn启动方式:在
__main__开头添加:if __name__ == '__main__': multiprocessing.set_start_method('spawn') # 后续代码不变spawn会增加进程启动开销,但能彻底隔离CUDA/OpenMP资源,避免fork带来的上下文污染。 - 严格控制进程数与GPU绑定:
- 进程数不要超过GPU数量×单GPU可承载并行任务数,建议设置为4×8=32(L4单卡可支持8个左右并行CUDA任务),远低于96或40,减少CPU调度竞争。
- 确保每个进程绑定固定GPU,避免跨GPU调度开销,可在
run_sim开头添加CUDA设备设置:import torch # 或直接调用CUDA API torch.cuda.set_device(gpu_device_list[0]) # 假设每个任务绑定单个GPU
- 优化磁盘IO:
- 将子任务的磁盘写入改为批量操作,减少随机IO;使用
os.sync()或异步IO库(如aiofiles)避免进程因等待磁盘刷新而阻塞。 - 检查AWS实例的磁盘类型,确保使用IO2/io3高性能SSD,避免磁盘带宽成为瓶颈。
- 将子任务的磁盘写入改为批量操作,减少随机IO;使用
- 监控进程状态与资源:
- 用
htop观察进程的CPU/GPU占用、IO等待时间;用nvidia-smi dmon监控GPU的利用率、内存和带宽。 - 用
strace -p <pid>跟踪阻塞进程的系统调用,定位具体的等待点(如锁、IO、CUDA内核调用)。
- 用
- 调整OpenMP线程数:
- 在环境变量中限制每个进程的OpenMP线程数,避免多进程的OpenMP线程抢占CPU:
import os os.environ['OMP_NUM_THREADS'] = '2' # 每个进程用2个OpenMP线程,总线程数=32×2=64 < 96vCPU
- 在环境变量中限制每个进程的OpenMP线程数,避免多进程的OpenMP线程抢占CPU:
内容的提问来源于stack exchange,提问作者betamax
相关产品推荐
相关产品推荐

