多进程调用RetinaFace提取人脸时偶发进程挂起问题排查
问题诊断与修复方案
一、进程数不稳定的原因与修复
你的concurent_tasks函数存在逻辑漏洞:内层循环遍历targets,会为每个目标函数创建num_processes个进程。如果targets包含多个函数,进程总数会远超预期;如果split_dir_to_2Dlists拆分出空的子列表,对应进程启动后会直接退出,看起来像是没启动。
修复步骤:
- 确认
targets仅传入extract_faces一个函数(符合你的人脸提取场景) - 检查
split_dir_to_2Dlists的拆分逻辑,确保每个子列表至少包含一个目录,避免空列表导致进程无任务可执行 - 简化进程创建逻辑,避免重复创建进程:
def concurent_tasks(target, num_processes, dir_list, *args): processes = [] list2D = split_dir_to_2Dlists(dir_list, num_processes) # 过滤空的子列表 list2D = [sub for sub in list2D if sub] for i, sub_dirs in enumerate(list2D): new_args = [sub_dirs] + list(args) p = multiprocessing.Process(target=target, args=new_args, name=f"process_{i}") processes.append(p) for p in processes: p.start() for p in processes: p.join() # join后进程已结束,无需调用terminate
二、进程挂起的核心原因与修复
1. 宽泛的异常捕获掩盖错误
extract_faces里的except: pass会捕获所有异常(包括死锁、资源耗尽、RetinaFace内部错误等),导致进程卡死时无任何报错信息。比如RetinaFace处理损坏图片时可能陷入无限等待,或内存分配失败导致进程静默挂起。
修复:
替换宽泛的异常捕获,只捕获预期错误并打印详情:
try: # 原有人脸提取逻辑 except Exception as e: print(f"处理文件 {entry.path} 出错: {str(e)}") continue
2. 频繁调用gc.collect()无意义且拖慢性能
连续4次调用gc.collect()完全多余,Python垃圾回收机制会自动处理,手动频繁调用反而增加CPU开销,甚至影响进程调度。直接删除这些冗余调用。
3. RetinaFace多进程模型加载冲突
多进程环境下,每个进程重复加载RetinaFace模型可能引发内存竞争或初始化死锁。建议在子进程启动后统一加载模型,或使用multiprocessing.Pool配合初始化函数优化:
from multiprocessing import Pool def init_worker(): # 子进程初始化时加载模型,避免重复加载 global retina_face_model retina_face_model = RetinaFace.build_model() def process_subdir(sub_dir_args): org_dir_subset, new_dir_path = sub_dir_args for dir in org_dir_subset: counter = 0 folder_name = dir.split('/')[-1] start = time.time() for entry in os.scandir(dir): try: faces = RetinaFace.extract_faces(entry.path, align=True, model=retina_face_model) if faces: new_folder_path = os.path.join(new_dir_path, folder_name) os.makedirs(new_folder_path, exist_ok=True) # 简化目录创建 for i, face in enumerate(faces): new_file_name = f"{folder_name}_{counter}_{i}.jpg" new_file_path = os.path.join(new_folder_path, new_file_name) cv2.imwrite(new_file_path, cv2.cvtColor(face, cv2.COLOR_RGB2BGR)) counter += 1 except Exception as e: print(f"处理 {entry.path} 失败: {str(e)}") stop = time.time() print(f"子文件夹 {dir} 处理耗时: {stop - start:.2f}s") def concurent_tasks(num_processes, dir_list, new_dir_path): list2D = split_dir_to_2Dlists(dir_list, num_processes) list2D = [sub for sub in list2D if sub] task_args = [(sub, new_dir_path) for sub in list2D] with Pool(num_processes, initializer=init_worker) as pool: pool.map(process_subdir, task_args)
4. 内存回落大概率是进程崩溃
活动监视器显示内存从15GB降到4GB,本质是部分进程因未捕获的异常崩溃退出,但原代码无错误输出,看起来像是“挂起”。添加异常日志后即可定位具体原因。
三、其他优化建议
- 用
os.makedirs(new_folder_path, exist_ok=True)替代手动判断目录是否存在,代码更简洁 - 减少循环中
entry.path的打印频率,过多IO操作会拖慢处理速度,可改为仅在出错时打印 - 通过
resource模块限制单个进程的内存上限,避免因内存占用过高被系统强制终止
内容的提问来源于stack exchange,提问作者Rohaan Manzoor
相关产品推荐
相关产品推荐

