多进程场景下进程意外终止后,如何避免恢复时的阻塞问题?
解决多进程恢复中join()阻塞的问题
核心问题原因
你遇到的阻塞问题本质是join()方法的特性:调用后会阻塞当前进程,直到目标进程执行完毕。在恢复函数中调用它,会导致恢复进程卡在等待新启动的业务进程结束,无法继续监控其他进程的状态。
正确实现思路
- 移除恢复函数中的
join():重启业务进程后,不需要等待它完成,恢复函数应立即回到监控循环,继续检查其他进程状态。 - 使用进程间共享的PID列表:原代码中直接传递普通列表给子进程,修改的只是副本,其他进程无法获取更新后的PID。需要用
multiprocessing.Manager创建可共享的列表。 - 保持监控循环的独立性:恢复进程的核心是持续轮询所有目标进程状态,无需绑定到单个重启进程的生命周期。
修改后的完整代码
import multiprocessing as mp import time import random import psutil import os def proc_1(timepass=1, repeat=20): repeated = True i = 0 while repeated: x = random.uniform(0, timepass) i += 1 if i < repeat: print(f"Proc_1: {i}, will fall asleep for: {x}") else: repeated = False print("Proc_1 COMPLETED!") break time.sleep(x) def proc_2(timepass=1, repeat=20): repeated = True i = 0 while repeated: x = random.uniform(0, timepass) i += 1 if i < repeat: print(f"Proc_2: {i}, will fall asleep for: {x}") else: repeated = False print("Proc_2 COMPLETED!") break time.sleep(x) def process_killer(PID_list, timepass, exclusion_list=[]): while True: x = random.uniform(0, timepass) time.sleep(x) this_process_pid = os.getpid() parent_process = psutil.Process(os.getppid()) child_pid_list = [p.pid for p in parent_process.children(recursive=True) if p.pid != this_process_pid] child_pid_list = list(set(child_pid_list) - set(exclusion_list)) print('\n'.join([f'Process_{i + 1} PID: {child_pid_list[i]}' for i in range(len(child_pid_list))])) if len(child_pid_list) > 0: number = random.randint(0, len(child_pid_list)-1) if len(child_pid_list)>1 else 0 try: kill_proc = psutil.Process(child_pid_list[number]) if kill_proc.name() == "python.exe" and psutil.pid_exists(kill_proc.pid): print(f"We kill the process with PID {kill_proc.pid}") kill_proc.kill() print(f"Process with PID {kill_proc.pid} killed.") except psutil.NoSuchProcess: print(f"Process with PID {child_pid_list[number]} not found.") def process_recovery(process_pid_list): while True: for i in range(len(process_pid_list)): if not psutil.pid_exists(process_pid_list[i]): print(f'Process with PID {process_pid_list[i]} is dead') print(f'Restoring the process') # 根据索引重启对应业务进程 if i == 0: process = mp.Process(target=proc_1, kwargs={'timepass': 2, 'repeat': 30}) else: process = mp.Process(target=proc_2, kwargs={'timepass': 3, 'repeat': 30}) process.start() # 更新共享列表中的PID process_pid_list[i] = process.pid temp_str = '\n'.join([f'Process_{idx + 1} PID: {process_pid_list[idx]}' for idx in range(len(process_pid_list))]) print('Recovery result:\n' + temp_str) # 移除join(),避免阻塞 # process.join() time.sleep(0.2) if __name__ == "__main__": # 使用Manager创建进程间共享的列表 with mp.Manager() as manager: PID_list = manager.list() # 启动初始业务进程 process_1 = mp.Process(target=proc_1, kwargs={'timepass': 2, 'repeat': 30}) process_2 = mp.Process(target=proc_2, kwargs={'timepass': 3, 'repeat': 30}) process_1.start() process_2.start() PID_list.append(process_1.pid) PID_list.append(process_2.pid) # 启动恢复进程 process_recov = mp.Process(target=process_recovery, kwargs={'process_pid_list': PID_list}) process_recov.start() # 启动杀手进程,排除恢复进程PID process_kill = mp.Process(target=process_killer, kwargs={'PID_list': PID_list, 'timepass': 10, 'exclusion_list': [process_recov.pid]}) process_kill.start() print("Main PID:", os.getpid()) print("Process_1 PID:", process_1.pid) print("Process_2 PID:", process_2.pid) print("Process_killer PID:", process_kill.pid) print("Process_recovery PID:", process_recov.pid) # 主进程等待所有子进程(可选,根据需求调整) process_1.join() process_2.join() process_kill.join() process_recov.join() time.sleep(5) print("Program completed")
关键修改说明
- 移除
process.join():恢复进程启动新业务进程后,立即回到监控循环,不会被单个进程的执行周期阻塞。 - 使用
manager.list():解决了普通列表在进程间无法共享更新的问题,确保杀手进程和恢复进程都能获取最新的PID。 - 简化进程PID获取逻辑:优化了
process_killer中获取子进程列表的代码,更简洁高效。
内容的提问来源于stack exchange,提问作者rack
相关产品推荐
相关产品推荐

