关于进程的理论知识这里简要带过,本文主要通过具体示例对Python中多进程的处理进行一个简要总结。
进程
进程是操作系统进行资源分配和调度的基本单位。Python 标准库 os 和 multiprocessing 中都包含了对进程的操作。multiprocessing 包中进程的处理对开发人员更加友好,其底层其实就是应用 os 包中进程处理的函数,而 os 包中的进程处理函数与C中标准函数对应。
multiprocessing
from multiprocessing import Process
def call(msg):
print(msg)
process = Process(target=call, args=("hello",))
process.start()
process.join()
我们可以看到,创建一个进程如此方便。
os
import os
process = os.fork()
if process == 0:
print("child process")
elif process > 0:
print("parent process")
os.wait()
孤儿进程
如果子进程执行还没有结束,父进程就被杀死,那么子进程将变成孤儿进程,挂到init进程下。
import os
process = os.fork()
if process == 0:
print("child process")
sleep(100)
print("child process end!")
elif process>0:
print("parent process")
sleep(10)
print("parent process end!")
你可以通过ps -el
看到开始时父子进程都存在,10秒之后,父进程结束,子进程被挂到 init
进程下面。
如果你不想产生孤儿进程,想在父进程结束时子进程也随之结束,你可以使用第三方库prctl
,这个库对操作系统C标准库进行了封装,可以对进程进行定制处理。
import signal
import os
from time import sleep
import prctl
p = os.fork()
if p == 0:
prctl.prctl(prctl.PDEATHSIG, signal.SIGKILL)
print("child process")
sleep(100)
print("child end")
elif p > 0:
print("parent process")
sleep(10)
print("parent end")
Note:
除非你故意去kill父进程,否则使用 multiprocessing 不会产生孤儿进程。处理细节涉及到 daemon 这个属性,这个 daemon 属性与我们所说的守护进程没有半毛钱关系。daemon 属性是布尔类型,设置为 True 的时候,父进程退出之前会先杀死子进程,设置为 False 的时候,父进程退出之前会对子进程进行 wait。
僵死进程
子进程结束之后,在操作系统中还会记录它的一些运行信息,如返回状态、运行时间和进程号。此时,需要父进程进行资源清理回收。如果没有回收,就成了僵死进程。僵死进程生成了,而程序中又没有处理的话,它会一直存在,直到父进程结束。你可以通过杀死其父进程来回收掉僵死进程。虽然僵死进程占用的资源很少,但是如果僵死进程数堆积得越来越多,也会对系统性能产生影响。我们在程序中还是最好有对僵死进程的相关处理。有三种方式来避免僵死进程,其本质上都是wait回收。
僵死处理:join
直接在父进程中调用 join 函数,如上面示例中。但这样会阻塞父进程。
僵死处理:signal+wait
子进程结束后会发送一个SIGCHLD
信号给父进程,所以可以在父进程中设置一个信号处理函数处理对子进程进行回收。
from multiprocessing import Process
from time import sleep
import os
import signal
def sigchld_handler(signum, action):
try:
while True:
pid, info = os.waitpid(-1, os.WNOHANG)
if pid == 0:
break
status = info >> 8
print("subprocess: {0}, status: {1}".format(pid, status))
except OSError as e:
print(e.message)
signal.signal(signal.SIGCHLD, sigchld_handler)
def call(msg):
for i in range(2):
print(msg)
process = Process(target=call, args=("hello",))
process.start()
sleep(10)
输出:
hello
hello
subprocess: 11560, status: 0
NOTE: sleep(10) 在信号中断处理返回后将跳过而执行下一条指令;
僵死处理:init 托管
如果你不想自己等待回收,你也可以把它托管给init进程,让它处理僵死进程。只要在父进程中将 SIGCHLD
信号的处理函数设置成 SIG_IGN
就能将僵死态的进程直接交给 init 进程进行回收。
from signal import signal, SIGCHLD, SIG_IGN
signal(SIGCHLD, SIG_IGN)
守护进程
守护进程是指脱离了控制终端的后台运行进程。
import os
import sys
def daemonize (stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
try:
pid = os.fork()
if pid > 0:
sys.exit(0)
except OSError as e:
print >> sys.stderr, ("step1 - fork failed, errno: {}, str: {}\n".format(e.errno, e.strerror) )
sys.exit(1)
os.chdir(os.getcwd())
os.umask(0)
os.setsid()
try:
pid = os.fork()
if pid > 0:
sys.exit(0)
except OSError as e:
print >> sys.stderr, ("step2 - fork failed, errno: {}, str: {}\n" % (e.errno, e.strerror) )
sys.exit(1)
sys.stdout.flush()
sys.stderr.flush()
tmpin = open(stdin, 'r')
tmpout = open(stdout, 'a+')
tmperr = open(stderr, 'a+')
os.dup2(tmpin.fileno(), sys.stdin.fileno())
os.dup2(tmpout.fileno(), sys.stdout.fileno())
os.dup2(tmperr.fileno(), sys.stderr.fileno())