You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多进程调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 11:36:14