如何通过subprocess停止单个脚本生成的所有子进程

2 投票
2 回答
3007 浏览
提问于 2025-04-18 04:05

我需要对我的包中的一部分运行一个 unittest,最好的方法是控制它的启动,然后结束所有由 multiprocessing 模块产生的进程。

下面是我想说的内容:

test.py

import logging
import multiprocessing
import time
import random

log = logging.getLogger(__name__)

def start_consumers(conf, worker_count=5):
    manager = WorkerManager(conf, worker_count)
    manager.start()


class WorkerManager():
    def __init__(self, conf, worker_count=5):
        self.workers = []
        for num in range(worker_count):
            self.workers.append(WorkHandler(conf))

    def start(self):
        for worker in self.workers:
            worker.daemon = True
            worker.start()

        print 'started'
        for worker in self.workers:
            worker.join()

class WorkHandler(multiprocessing.Process):

    def __init__(self, conf, *args, **kwargs):
        super(WorkHandler, self).__init__(*args, **kwargs)
        self.conf = conf
        self.name = str(random.randint(0,100))

    def run(self):
        while True:
            print self.conf['foo'], self.name
            time.sleep(3)

if __name__ == "__main__":
    conf = {'foo': 'bar'}
    start_consumers(conf)

现在如果我从Linux终端运行这个测试,我可以看到打印的语句。如果我执行:ps aux | grep python,我可以看到所有生成的子进程:

sergey    4081  0.0  0.1  71684 13164 pts/3    S+   08:58   0:00 python
sergey    4108  0.3  0.0  39092  6472 pts/3    S+   08:59   0:00 python test.py
sergey    4109  0.0  0.0  39092  4576 pts/3    S+   08:59   0:00 python test.py
sergey    4110  0.0  0.0  39092  4568 pts/3    S+   08:59   0:00 python test.py
sergey    4111  0.0  0.0  39092  4576 pts/3    S+   08:59   0:00 python test.py
sergey    4112  0.0  0.0  39092  4584 pts/3    S+   08:59   0:00 python test.py
sergey    4113  0.0  0.0  39092  4580 pts/3    S+   08:59   0:00 python test.py
sergey    4115  0.0  0.0  13588   944 pts/7    S+   08:59   0:00 grep --color=auto python

现在如果我尝试用 subprocess 来运行同样的 test.py,一切都正常,直到我需要结束它们为止:

>>> import subprocess as s
>>> p = s.Popen(['python', 'test.py'], stdout=s.PIPE)

问题:

有什么优雅的方法可以处理这个变量 p,以便在我按下终端中的 Ctrl+C 时实现类似的行为?
换句话说,我想要的结果类似于 pkill -f "test.py"

更新:

p.kill()p.terminate() 并不能满足我的需求:

这是我执行其中任何一个后的结果:

sergey    4438  0.0  0.0      0     0 pts/3    Z+   09:16   0:00 [python] <defunct>
sergey    4439  0.0  0.0  39092  4572 pts/3    S+   09:16   0:00 python test.py
sergey    4440  0.0  0.0  39092  4568 pts/3    S+   09:16   0:00 python test.py
sergey    4441  0.0  0.0  39092  4576 pts/3    S+   09:16   0:00 python test.py
sergey    4442  0.0  0.0  39092  4580 pts/3    S+   09:16   0:00 python test.py
sergey    4443  0.0  0.0  39092  4580 pts/3    S+   09:16   0:00 python test.py

2 个回答

1

你可以尝试创建一个新的会话,并使用 os.killpg() 来结束所有子进程:

import os
import signal
from subprocess import Popen

p = Popen('python test.py', shell=True, preexec_fn=os.setsid)
# ... later
os.killpg(p.pid, signal.SIGTERM) # send signal to the process group

查看这个链接了解更多信息:如何终止一个使用 shell=True 启动的 Python 子进程

2

我觉得你想要的功能是 subprocess.terminate()subprocess.kill() 这两个方法,它们属于 子进程对象

你可能还需要结合一个在 atexit 模块中注册的方法,这个方法会遍历你所有的子进程并将它们终止。比如:

def terminate_children(children):
    for process in children:
        process.terminate()

...
# Somewhere else in your code
children = [s.Popen(['python', 'test.py'], stdout=s.PIPE) for i in range(number_processes)] # Spools up number_processes child processes
atexit.register(terminate_children, children) # where children is a list of subprocesses

这样做的话,当你优雅地终止父进程时,所有的子进程也会被优雅地终止。

如果你想从一个完全不同的脚本中杀掉这些进程,而没有直接引用子进程的代码,可以看看 这里。你基本上需要查看 Python 的 os 进程管理方法

撰写回答