如何正确终止通过subprocess运行的后台进程?
问题根源
你在subprocess.Popen()的参数里加了"&",这会让bash启动my_script.sh后立刻把脚本放到后台运行,随后bash进程自己直接退出。此时你拿到的process对象对应的是已经结束的bash父进程,调用kill()或terminate()自然对真正在后台运行的my_script.sh进程无效。
最佳解决办法
方案1:移除&,让subprocess直接管理进程
subprocess.Popen()本身就是异步启动进程的,不需要借助bash的&来后台运行。修改代码如下:
def my_method(): try: # 移除最后的"&"参数 process = subprocess.Popen(["bash", "my_script.sh"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) except Exception as e: print(f"Error running the script: {e}") return process def caller(): process = my_method() # 优雅终止进程 process.terminate() # 若需要强制杀死,用kill() # process.kill()
此时process对象直接对应运行my_script.sh的bash进程,调用terminate()或kill()会直接终止该bash进程,连带终止脚本(如果脚本没有自行fork独立子进程的话)。
方案2:使用进程组杀死所有关联进程
如果脚本本身会fork独立的后台子进程,仅终止bash父进程无法完全清理,可通过创建新进程组的方式,一次性杀死整个组内的所有进程:
import os import signal def my_method(): try: # 添加start_new_session=True创建新进程组 process = subprocess.Popen(["bash", "my_script.sh"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, start_new_session=True ) except Exception as e: print(f"Error running the script: {e}") return process def caller(): process = my_method() try: # 获取进程组ID,杀死整个进程组 pgid = os.getpgid(process.pid) os.killpg(pgid, signal.SIGTERM) # 优雅终止 # 强制杀死用下面的语句 # os.killpg(pgid, signal.SIGKILL) except ProcessLookupError: # 进程已提前退出,忽略即可 pass
这种方式能确保所有属于该脚本的关联进程都被终止,适合复杂的脚本场景。
方案3:记录脚本PID(不推荐)
如果你一定要保留&的写法,可在my_script.sh中写入自身PID到文件,再通过Python读取PID并杀死进程:
修改my_script.sh:
# 脚本开头添加PID记录 echo $$ > /tmp/my_script.pid # 原有脚本逻辑...
Python代码:
import os import signal def caller(): process = my_method() try: with open('/tmp/my_script.pid', 'r') as f: script_pid = int(f.read().strip()) os.kill(script_pid, signal.SIGTERM) except (FileNotFoundError, ProcessLookupError): pass
此方式存在PID文件被覆盖、误杀其他进程的风险,仅作为特殊场景的备选方案。
内容的提问来源于stack exchange,提问作者amin
相关产品推荐
相关产品推荐

