正确处理python中的子进程终止

时间:2018-10-31 16:38:04

标签: python parallel-processing queue multiprocessing python-3.7

我正在使用python 3.7并遵循此documentation。我想要一个进程,该进程应产生一个子进程,等待其完成任务,并获取一些信息。我使用以下代码:

if __name__ == '__main__':
    q = Queue()
    p = Process(target=some_func, args=(q,))
    p.start()
    print q.get()
    p.join()

当子进程正确完成时,这没有问题,并且工作正常,但是当我的子进程在完成之前终止时,问题就开始了。 在这种情况下,我的应用程序处于等待状态。

q.get()p.join()提供超时并不能完全解决问题,因为我想立即知道子进程已死亡,而不是等待超时。

另一个问题是q.get()上的超时会产生一个异常,我希望避免这种异常。

有人可以建议我以更优雅的方式解决这些问题吗?

1 个答案:

答案 0 :(得分:1)

队列和信号

一种可能性是注册一个信号处理程序,并使用它来传递哨兵值。 在Unix上,您可以在父级中处理SIGCHLD,但这不是您的选择。根据信号模块docs

  

在Windows上,只能使用SIGABRT,SIGFPE,SIGILL,SIGINT,SIGSEGV,SIGTERM或SIGBREAK调用signal()。

不确定是否通过Task-Manager杀死它会转化为SIGTERM,但您可以尝试一下。

要处理SIGTERM,您需要在子级中注册信号处理程序。

import os
import sys
import time
import signal
from functools import partial
from multiprocessing import Process, Queue

SENTINEL = None


def _sigterm_handler(signum, frame, queue):
    print("received SIGTERM")
    queue.put(SENTINEL)
    sys.exit()


def register_sigterm(queue):
    global _sigterm_handler
    _sigterm_handler = partial(_sigterm_handler, queue=queue)
    signal.signal(signal.SIGTERM, _sigterm_handler)


def some_func(q):
    register_sigterm(q)
    print(os.getpid())
    for i in range(30):
        time.sleep(1)
        q.put(f'msg_{i}')


if __name__ == '__main__':

    q = Queue()
    p = Process(target=some_func, args=(q,))
    p.start()
    for msg in iter(q.get, SENTINEL):
        print(msg)
    p.join()

示例输出:

12273
msg_0
msg_1
msg_2
msg_3
received SIGTERM

Process finished with exit code 0

Queue&Process.is_alive()

即使与Task-Manager一起使用,您的用例听起来也无法排除强行杀死,所以我认为您最好使用不依赖信号的方法。

您可以在循环中检查您的进程p.is_alive(),是否使用指定的queue.get()调用timeout并处理Empty异常:

import os
import time
from queue import Empty
from multiprocessing import Process, Queue

def some_func(q):
    print(os.getpid())
    for i in range(30):
        time.sleep(1)
        q.put(f'msg_{i}')


if __name__ == '__main__':

    q = Queue()
    p = Process(target=some_func, args=(q,))
    p.start()

    while p.is_alive():
        try:
            msg = q.get(timeout=0.1)
        except Empty:
            pass
        else:
            print(msg)

    p.join()

也可以避免出现异常,但是我不建议您这样做,因为您不会将等待时间花在“排队”上,从而降低了响应速度:

while p.is_alive():
    if not q.empty():
        msg = q.get_nowait()
        print(msg)
        time.sleep(0.1)


管道和Process.is_alive()

如果您打算每个孩子使用一个连接,则可以使用管道代替队列。比队列更高效 (安装在管道顶部),您可以使用multiprocessing.connection.wait立即等待多个对象的就绪状态。

  

multiprocessing.connection.wait(object_list,timeout = None)

     

等待直到object_list中的对象准备就绪。返回对象列表中已准备好的那些对象的列表。如果超时是浮动的,则呼叫最多会阻塞很多秒。如果超时为“无”,则它将无限期阻塞。负超时等于零超时。

     

对于Unix和Windows,如果对象是可读的Connection对象,则该对象可以出现在object_list中。   连接且可读的socket.socket对象;要么   Process对象的sentinel属性。   当可以从中读取数据或另一端已关闭时,连接或套接字对象已准备就绪。

     

Unix :等待(对象列表,超时)几乎等同于select.select(对象列表,[],[],超时)。区别在于,如果select.select()被信号中断,则会引发OSError且错误号为EINTR,而wait()不会。

     

Windows :object_list中的项必须是可等待的整数句柄(根据Win32函数WaitForMultipleObjects()的文档所使用的定义),也可以是带有fileno()方法,该方法返回套接字句柄或管道句柄。 (请注意,管道句柄和套接字句柄不是可等待的句柄。)

     

3.3版中的新功能。 docs

您可以使用它同时等待进程的哨兵属性和管道的父端。

import os
import time
from multiprocessing import Process, Pipe
from multiprocessing.connection import wait


def some_func(conn_write):
    print(os.getpid())
    for i in range(30):
        time.sleep(1)
        conn_write.send(f'msg_{i}')


if __name__ == '__main__':

    conn_read, conn_write = Pipe(duplex=False)
    p = Process(target=some_func, args=(conn_write,))
    p.start()

    while p.is_alive():
        wait([p.sentinel, conn_read])  # block-wait until something gets ready
        if conn_read.poll():  # check if something can be received
            print(conn_read.recv())
    p.join()
相关问题