使用multiprocess.Process调用exec()后,如何等待多进程执行完毕?
我需要等待多个进程执行完毕,因需在子进程执行前调整其oom_score,故使用multiprocess.Process。尝试通过multiprocessing.connection.wait结合进程的sentinel值实现等待,但代码输出的两个时间戳仅差数毫秒,wait()并未等待命令执行完毕。而pp.join()可正常生效,且子进程非守护进程,Python会等待其完成。
参考代码:
import multiprocessing import multiprocessing.connection import os from datetime import datetime def adj_oom_score_exec(*command: str): with open("/proc/{}/oom_score_adj".format(os.getpid()), "w") as f: f.write("500") os.execv(command[0], command) exit(198) pp = multiprocessing.Process(target=adj_oom_score_exec, args=["/usr/bin/sleep", "1"]) pp.start() print(datetime.now()) multiprocessing.connection.wait([pp.sentinel]) print(datetime.now())
原因分析
multiprocessing.Process的sentinel是关联原Python子进程的文件描述符。当子进程调用os.execv后,原Python进程会被新的进程(比如示例中的sleep)替换,原Python进程的资源被立即释放,导致sentinel对应的管道触发可读状态,multiprocessing.connection.wait()因此提前返回,但此时execv启动的新进程仍在运行。
而pp.join()生效的原因是:join()等待的是进程对象对应的PID终止,execv不会改变进程PID,因此join()会一直等到execv启动的进程结束。
解决方案
方案1:用subprocess替代execv,让原Python子进程等待命令完成
修改子进程函数,使用subprocess.run()启动命令并等待其执行完毕,这样原Python子进程会在命令结束后才退出,sentinel的wait()就能正确等待整个流程完成:
import multiprocessing import multiprocessing.connection import os from datetime import datetime import subprocess def adj_oom_score_exec(*command: str): with open(f"/proc/{os.getpid()}/oom_score_adj", "w") as f: f.write("500") # 等待命令执行完毕后,子进程才会退出 subprocess.run(command) pp = multiprocessing.Process(target=adj_oom_score_exec, args=["/usr/bin/sleep", "1"]) pp.start() print(datetime.now()) multiprocessing.connection.wait([pp.sentinel]) print(datetime.now())
方案2:直接使用Process.join()等待多进程
如果不需要用multiprocessing.connection.wait(),直接遍历所有进程调用join()即可,这也是最直接的方式:
import multiprocessing import os from datetime import datetime import subprocess def adj_oom_score_exec(*command: str): with open(f"/proc/{os.getpid()}/oom_score_adj", "w") as f: f.write("500") subprocess.run(command) # 创建多个进程 processes = [ multiprocessing.Process(target=adj_oom_score_exec, args=["/usr/bin/sleep", "1"]), multiprocessing.Process(target=adj_oom_score_exec, args=["/usr/bin/sleep", "2"]) ] # 启动所有进程 for p in processes: p.start() print(datetime.now()) # 等待所有进程完成 for p in processes: p.join() print(datetime.now())
方案3:手动跟踪execv后的进程PID(不推荐)
如果坚持使用os.execv,可以在子进程中记录当前PID(即execv后的进程PID),然后在主进程中用os.waitpid()等待该PID。但这种方式需要进程间传递PID,复杂度较高,不如前两种方案简洁。
内容的提问来源于stack exchange,提问作者KamilCuk

