python中父子进程

最近在使用python中的multiprocessing模块时遇到一些问题,很多人应该遇到相同问题,简单研究下,供有需要的参考。

首先,要明白multiprocessing的出现很大程度是为了解决python GIL锁带来的多线程低效问题,其次,注意Windows上和Linux上的进程、线程行为不一致。

那么我们常遇到的问题如下:

1.父进程开新的子进程完成任务,父进程关闭时,必须关闭子进程

2.父进程被强制关闭时,子进程也必须关闭

3.子进程被强制关闭时,父进程也必须关闭

4.父子进程没必然联系,关闭互不影响

下面就从这四个问题出发,讨论每种问题的处理方式和在不同系统下的差别。

1.父进程关闭时,必须关闭子进程

a.常规情况

python 10.py执行如下代码

#!/usr/bin/python2.7
# -*- coding: utf-8-*-

from multiprocessing import Process
import os
import time

processes = []


def run_proc(name):
    print('Run child process %s (%s)...' % (name, os.getpid()))
    time.sleep(30)
    print('Exit child process %s (%s)...' % (name, os.getpid()))


if __name__ == '__main__':
    print('Parent process %s start.' % os.getpid())
    for i in xrange(0, 5):
        p = Process(target=run_proc, args=('test %s' %i,))
        p.start()
        processes.append(p)

    time.sleep(10)
    print('Parent process end.')

在Windows上和Linux上均输出

可以看到,显示Parent process end.后还需等待一段时间子进程关闭后父进程才能退出,此时ps -ux或任务管理器查看无子进程,默认父进程关闭时会等待子进程运行完毕才退出

b.子进程设为守护进程

我们知道multiprocessing.Process有一个参数设置进程为守护进程p.daemon = True,此时子进程可以独立运行。

python 11.py执行如下代码:

#!/usr/bin/python2.7
# -*- coding: utf-8-*-

from multiprocessing import Process
import os
import time

processes = []


def run_proc(name):
    print('Run child process %s (%s)...' % (name, os.getpid()))
    time.sleep(30)
    print('Exit child process %s (%s)...' % (name, os.getpid()))


if __name__ == '__main__':
    print('Parent process %s start.' % os.getpid())
    for i in xrange(0, 5):
        p = Process(target=run_proc, args=('test %s' %i,))
        p.daemon = True
        p.start()
        processes.append(p)

    time.sleep(10)
    print('Parent process end.')

在Windows和Linux上执行均输出:

显示Parent process end.后主进程直接退出,此时ps -ux或任务管理器查看无子进程,即父进程关闭时强制杀死了子进程。

为了取得和之前一样的效果,可以显示指定等待子进程执行完成后再退出,即指定p.join()等待子进程执行完成,代码如下,此时和之前运行效果一样。

#!/usr/bin/python2.7
# -*- coding: utf-8-*-

from multiprocessing import Process
import os
import time

processes = []


def run_proc(name):
    print('Run child process %s (%s)...' % (name, os.getpid()))
    time.sleep(30)
    print('Exit child process %s (%s)...' % (name, os.getpid()))


if __name__ == '__main__':
    print('Parent process %s start.' % os.getpid())
    for i in xrange(0, 5):
        p = Process(target=run_proc, args=('test %s' %i,))
        p.daemon = True
        p.start()
        processes.append(p)

    time.sleep(10)
    print('Parent process end.')

    for p in processes:
        p.join()

c.子进程响应关闭消息

既然父进程关闭时会强制杀死守护子进程,那么不妨加上进程关闭消息响应,实现优雅关闭,执行python 12.py如下:

#!/usr/bin/python2.7
# -*- coding: utf-8-*-

from multiprocessing import Process
import os
import time
import signal

processes = []


def term(sig_num, addtion):
    print '%d to term child process' % os.getpid()


def run_proc(name):
    print('Run child process %s (%s)...' % (name, os.getpid()))
    while True:
        time.sleep(10)
        print('Process %s' % (os.getpid()))
    print('Exit child process %s (%s)...' % (name, os.getpid()))


if __name__ == '__main__':
    signal.signal(signal.SIGTERM, term)

    print('Parent process %s start.' % os.getpid())
    for i in xrange(0, 5):
        p = Process(target=run_proc, args=('test %s' %i,))
        p.daemon = True
        p.start()
        processes.append(p)

    time.sleep(10)
    print('Parent process end.')

此时Windows上显示如下:

windows还是强制关闭,父进程和子进程都关闭

Linux显示如下:

Linux上显示Parent process end.后主进程通知子进程关闭,子进程响应了了这一消息,但是主循环仍然在持续输出,此时子进程和父进程都还存在。

 这就涉及到Windows和Linux上进程结束的特点:Windows上没有一个很好的机制来通知进程关闭,这里通过TerminateProcess来强杀进程;Linux上可以给进程发送signal.SIGTERM来通知进程关闭,如果进程signal.signal注册了这个消息的响应,那么接到通知后进程如何处理退出是自己的事了,甚至可以像本示例不退出,如果不注册这个消息的响应则像上个示例一样进程被强杀

如上逻辑,可以查看对应的源码如下:

multiprocessing.Process类中_bootstrap函数初始化运行,可以看到这里结束时都调用了util._exit_function()函数

    def _bootstrap(self):
    ...
            try:
                self.run()
                exitcode = 0
            finally:
                util._exit_function()
    ...

查看multiprocessing.util中util._exit_function()函数如下

...
if current_process() is not None:

    for p in active_children():
        if p._daemonic:
            info('calling terminate() for daemon %s', p.name)
            p._popen.terminate()

    for p in active_children():
        info('calling join() for process %s', p.name)
        p.join()
...

可以看到,结束时会遍历当前进程的子进程,先对每个子进程发送terminate通知,然后等待剩余存活子进程执行完毕。多说一点,注意这里的逻辑,也就是如果子进程(包括守护进程)不全部退出,那么父进程是不退出的

在multiprocessing.forking中查看teminate代码如下,可以看到Windows下是强制结束进程,Linux下是发送通知信号。

if sys.platform != 'win32':
...#linux
    def terminate(self):
        if self.returncode is None:
            try:
                os.kill(self.pid, signal.SIGTERM)
            except OSError, e:
                if self.wait(timeout=0.1) is None:
                    raise
else:
...#windows                        
    def terminate(self):
        if self.returncode is None:
            try:
                _subprocess.TerminateProcess(int(self._handle), TERMINATE)
            except WindowsError:
                if self.wait(timeout=0.1) is None:
                    raise
...

2.父进程被强制关闭时,关闭子进程

Windows中,执行python 10.py或python 11.py,任务管理器中结束父进程,查看子进程是否关闭;linux中执行python 10.py &或python 11.py &,kill 主进程pid结束父进程,ps -ux | grep python查看子进程是否关闭。

可以看到在Windows和Linux中强制关闭父进程,子进程仍然存活,被称为孤立进程

要想主进程被强制关闭时,也关闭子进程,应该怎么做呢。前面说过Windows上结束进程没有通知机制,因此暂时没有简单办法处理;在linux上kill会给被关闭进程发送通知,既然这样,我们响应这个消息并给子进程发送关闭消息即可,代码如下:

#!/usr/bin/python2.7
# -*- coding: utf-8-*-

from multiprocessing import Process
import os
import time
import signal

processes = []


def term(sig_num, addtion):
    print '%d to term child process' % os.getpid()

    try:
        for p in processes:
            print 'process %d-%d terminate' % (os.getpid(), p.pid)
            p.terminate()
            # os.kill(p.pid, signal.SIGKILL)
    except Exception as e:
        print str(e)


def run_proc(name):
    print('Run child process %s (%s)...' % (name, os.getpid()))
    time.sleep(100)
    print('Exit child process %s (%s)...' % (name, os.getpid()))


if __name__ == '__main__':
    print('Parent process %s start.' % os.getpid())
    for i in xrange(0, 5):
        p = Process(target=run_proc, args=('test %s' %i,))
        p.daemon = True
        p.start()
        processes.append(p)

    signal.signal(signal.SIGTERM, term)
    time.sleep(10)
    print('Parent process end.')

在linux上执行python 20.py &,kill 主进程pid结束父进程,输出如下,此时ps -ux | grep python查看子进程全部关闭。可以看到代码中使用p.terminate来通知子进程关闭,前面说了这是一种友好的通知方法,如果子进程响应但是不退出也可以,如果强制子进程关闭可以直接调用os.kill(p.pid, signal.SIGKILL)发送SIGKILL消息,相当于shell中kill -9效果。注意代码中注册SIGTERM信号的位置,不能在子进程创建前注册,否则子进程也会响应term中又会对子进程发送消息,会形成死循环。

 现在再说回Windows上的进程关闭,如何实现父进程关闭子进程关闭呢?显然我们需要就是一个父进程响应关闭并在实际关闭前清理子进程,这一点可以借助windows的消息机制的来处理,可在父进程中内建一个窗口接收消息,父进程响应消息后关闭子进程,使用消息通知父进程关闭即可达到和linux上一样效果,这也是在Windows上实现优雅关闭的常用方法(一般是查找主窗口发送WM_CLOSE消息)。

3.子进程被强制关闭时,关闭父进程

很自然思考子进程强制关闭时必须关闭父进程的情况,这种情况一般适用于父进程对子进程有严重依赖的地方,如父进程调试子进程等等。有了前面的探讨,很自然想到可以把父进程pid传给子进程,在通知子进程关闭时(Windows借助消息完成通知)通知父进程关闭。还有别的方法吗?

先说Linux,主进程中创建子进程时,父进程与其创建的子进程称为进程组,同一个进程组中的进程,它们的进程组ID是一致的。利用python标准库中os.getpgid方法,获取进程对应的组ID,在子进程响应关闭消息中调用os.killpg方法,向进程的组ID发送信号即可,python 30.py代码如下:

from multiprocessing import Process
import os
import time
import signal

processes = []


def term(sig_num, addtion):
    print 'current pid is %s, group id is %s' % (os.getpid(), os.getpgrp())
    os.killpg(os.getpgid(os.getpid()), signal.SIGKILL)


def run_proc(name):
    print('Run child process %s (%s)...' % (name, os.getpid()))
    time.sleep(50)
    print('Exit child process %s (%s)...' % (name, os.getpid()))


if __name__ == '__main__':
    signal.signal(signal.SIGTERM, term)

    print('Parent process %s start.' % os.getpid())
    for i in xrange(0, 5):
        p = Process(target=run_proc, args=('test %s' %i,))
        p.daemon = True
        p.start()
        processes.append(p)

    time.sleep(100)
    print('Parent process end.')

运行结果如下:

Windows中有个作业(Job)的概念,类似进程组,可以完成相同功能;还可以使用windows的调试功能,父进程WaitForDebugEvent接收子进程的调试事件,捕获到子进程退出时,父进程也退出,这些都可以完成功能,但是必须间接调用windows系统接口了,恕不赘述。

4.父子进程没必然联系,关闭互不影响

这种情况不多,简单提下,其实multiprocessing就是对linux和windows上的接口做了包装,同时管理了父进程和子进程的关系,要想达到互不影响的目的,直接绕开multiprocessing模块调用对应系统接口接口

参考multiprocessing源码,linux上调用os.fork(),windows上调用 _subprocess.CreateProcess即可,恕不赘述。

演示代码下载链接

原创,转载请注明来自http://blog.csdn.net/wenzhou1219

猜你喜欢

转载自blog.csdn.net/wenzhou1219/article/details/81320622
今日推荐