Python:简单方法终止子进程或报告其成功?

5 投票
2 回答
3748 浏览
提问于 2025-04-16 02:16

我想要做的事情是:

  1. 同时运行一些命令(比如下面的'sleep'命令),
  2. 报告每个命令的开始和结束情况,
  3. 能够通过'kill -9 parent_process_pid'来终止它们。

关于这些事情已经有很多资料了,但我觉得我还没找到我想要的优雅的Python解决方案。我也希望能让这些内容对完全不懂Python的人来说相对容易理解(而且简短)。

到目前为止,我的做法是(见下面的代码):

  1. 把subprocess.call(unix_command)放在一个包装函数里,这个函数会报告命令的开始和结束。
  2. 用multiprocess.Process来调用这个包装函数。
  3. 跟踪合适的进程ID,把它们存储在全局变量里,并在信号处理器中终止它们。

我试图避免那种定期检查进程状态的解决方案,但我也不太确定为什么。

有没有更好的方法呢?

import subprocess,multiprocessing,signal
import sys,os,time

def sigterm_handler(signal, frame):
        print 'You killed me!'
        for p in pids:
                os.kill(p,9)
        sys.exit(0)

def sigint_handler(signal, frame):
        print 'You pressed Ctrl+C!'
        sys.exit(0)

signal.signal(signal.SIGINT, sigint_handler)
signal.signal(signal.SIGTERM, sigterm_handler)

def f_wrapper(d):
        print str(d) + " start"
        p=subprocess.call(["sleep","100"])
        pids.append(p.pid)
        print str(d) + " done"

print "Starting to run things."

pids=[]

for i in range(5):
        p=multiprocessing.Process(target=f_wrapper,args=(i,))
        p.daemon=True
        p.start()

print "Got things running ..."

while pids:
        print "Still working ..."
        time.sleep(1)

2 个回答

2

这段代码(下面的代码)对我来说似乎很好用,可以通过“top”命令或者在命令行按ctrl-c来终止它。唯一真正的变化是把subprocess.Process替换成了subprocess.Popen(我觉得subprocess.Process这个东西是不存在的)。

这里的代码还可以改进一下,比如通过某种方式锁定标准输出,这样就不会出现不同进程之间打印内容重叠的情况。

import subprocess, threading, signal
import sys, time

pobs = set()                            # set to hold the active-process objects
pobslock = threading.Lock()     # a Lock object to make sure only one at a time can modify pobs

def numpobs():
        with pobslock:
                return len(pobs)

# signal handlers
def sigterm_handler(signal, frame):
        print 'You killed me! I will take care of the children.'
        with pobslock:
                for p in pobs: p.kill()
        sys.exit(0)

def sigint_handler(signal, frame):
        print 'You pressed Ctrl+C! The children will be dealt with automatically.'
        sys.exit(0)

signal.signal(signal.SIGINT, sigint_handler)
signal.signal(signal.SIGTERM, sigterm_handler)


# a function to watch processes
def p_watch(d, p):
        print d, 'start', p.pid
        rc = p.wait()
        with pobslock:
                pobs.remove(p)
        print d, 'done, rc =', rc


# the main code
print "Starting to run things ..."
for i in range(5):
        p = subprocess.Popen(['sleep', '4'])
        with pobslock:
                pobs.add(p)
        # create and start a "daemon" to watch and report the process p.
        t = threading.Thread(target=p_watch, args=(i, p))
        t.daemon=True
        t.start()

print "Got things running ..."
while numpobs():
        print "Still working ..."
        time.sleep(1)
4

一旦 subprocess.call 执行完毕,子进程就结束了,而 call 的返回值就是子进程的 returncode。所以,把这些返回码放到列表 pids 中(顺便说一下,这个列表在多进程中是不同步的,也就是说,添加它的多个进程和“主”进程之间的数据是不一致的),然后把它们当作进程ID发送 9 信号,这样做肯定是错的。

还有一个问题是,问题的描述也有问题:

应该能够用 'kill -9 parent_process_pid' 来终止它们。

因为 -9 的意思是父进程根本无法拦截这个信号(这就是明确指定 -9 的目的)——我想这里的 -9 是多余的。

你应该使用 threading 而不是 multiprocessing(每个“看护者”线程或进程基本上只是等待它的子进程,所以为什么要在这么轻量的任务上浪费进程呢?);你还应该在主线程中调用 subprocess.Process(这样可以启动子进程,并获得它的 .pid,然后放入列表中),并把得到的进程对象传给看护者线程,让它等待这个进程(当进程结束时,它会报告并从列表中移除)。子进程ID的列表应该用锁来保护,因为主线程和多个看护者线程都可以访问它,而且使用集合(set)可能比列表(list)更好(因为删除速度更快),因为你不关心顺序,也不需要避免重复。

所以,大致上(没有测试,可能会有bug;-) 我会把你的代码改成类似这样的:

import subprocess, threading, signal
import sys, time

pobs = set()
pobslock = threading.Lock()
def numpobs():
    with pobslock:
        return len(pobs)

def sigterm_handler(signal, frame):
    print 'You killed me!'
    with pobslock:
        for p in pobs: p.kill()
    sys.exit(0)

def sigint_handler(signal, frame):
    print 'You pressed Ctrl+C!'
    sys.exit(0)

signal.signal(signal.SIGINT, sigint_handler)
signal.signal(signal.SIGTERM, sigterm_handler)

def f_wrapper(d, p):
    print d, 'start', p.pid
    rc = p.wait()
    with pobslock:
        pobs.remove(p)
    print d, 'done, rc =', rc

print "Starting to run things."

for i in range(5):
    p = subprocess.Popen(['sleep', '100'])
    with pobslock:
        pobs.add(p)
    t = threading.Thread(target=f_wrapper, args=(i, p))
    t.daemon=True
    t.start()

print "Got things running ..."

while numpobs():
    print "Still working ..."
    time.sleep(1)

撰写回答