Python Library: multiprocessing (1)

qyuhen 2010-06-20 12:56:46
Python 新手(接触时间很早,最近才开始正式使用),欢迎交流指正。

个人 Python 学习笔记

============================================================

Python Standard Library 从 2.6 起增加了子进程级别的并行开发支持 —— multiprocessing。


#!/usr/bin/env python
# -*- coding:utf-8 -*-

import os, time
from multiprocessing import *

def test(x):
print current_process().pid, x
time.sleep(1)

if __name__ == "__main__":
print "main:", os.getpid()
p = Pool(5)
p.map(test, range(13))


输出:


$ ./test.py

main: 1208
1209 0
1210 1
1211 2
1212 3
1213 4
1210 5
1209 6
1212 7
1211 8
1213 9
1209 10
1210 11
1212 12


从输出结果,我们可以看出多个子进程 "并行" 完成了 "计算" 任务。

1. Process

我们先最根本的 Process 入手,看看是如何启动子进程完成并行计算的。上面的 Pool 不过是创建多个 Process,然后将数据(args)提交给多个子进程完成而已。


#!/usr/bin/env python
# -*- coding:utf-8 -*-

import os, time
from multiprocessing import *

def test(x):
print current_process().pid, x
time.sleep(1)

if __name__ == "__main__":
print "main:", os.getpid()

p = Process(target = test, args = [100])
p.start()
p.join()


输出:


$ ./test.py

main: 1229
1230 100


为了更好掌握这个 "并行" 利器,需要 "深入" 探究子进程的执行流程和生命周期。multiprocessing 在接口设计上参考了 threading,而我们分析的起点就是 Process.start()。


class Process(object):
'''
Process objects represent activity that is run in a separate process

The class is analagous to `threading.Thread`
'''

_Popen = None

def __init__(self, group=None, target=None, name=None, args=(), kwargs={}):
...
self._parent_pid = os.getpid()
self._popen = None
self._target = target
self._args = tuple(args)
self._kwargs = dict(kwargs)
...

def start(self):
...

if self._Popen is not None:
Popen = self._Popen
else:
from .forking import Popen

self._popen = Popen(self)
_current_process._children.add(self)


继续,这个 Popen 是关键,打开 forking.py。


class Popen(object):

def __init__(self, process_obj):
...
self.pid = os.fork()
if self.pid == 0:
...
code = process_obj._bootstrap()
sys.stdout.flush()
sys.stderr.flush()
os._exit(code)


这里调用了关键的 fork(),子进程由此而生。接下来子进程调用了 Process._bootstrap()。注意,这里还调用了 "os._exit(code)",这表明子进程与 Process.Start() "self._popen = Popen(self)" 之后的代码分道扬镳。


class Process(object):

def _bootstrap(self):
from . import util
global _current_process

try:
self._children = set()
self._counter = itertools.count(1)

try:
sys.stdin.close()
sys.stdin = open(os.devnull)
except (OSError, ValueError):
pass

_current_process = self # 重置了 _current_process !
util._finalizer_registry.clear()
util._run_after_forkers()
util.info('child process calling self.run()')

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

except SystemExit, e:
if not e.args:
exitcode = 1
elif type(e.args[0]) is int:
exitcode = e.args[0]
else:
sys.stderr.write(e.args[0] + '\n')
sys.stderr.flush()
exitcode = 1
except:
exitcode = 1
import traceback
sys.stderr.write('Process %s:\n' % self.name)
sys.stderr.flush()
traceback.print_exc()

util.info('process exiting with exitcode %d' % exitcode)
return exitcode


这个方法看着复杂,初始化子进程环境,并对异常等做出处理,但关键的就是 "self.run()" 这一行。


class Process(object):
def run(self):
if self._target:
self._target(*self._args, **self._kwargs)


子进程在这里调用了我们创建 Process 对象时传递的 target 参数,也就是上例中的 test()。如此,我们就大概明白了子进程创建和调用的整个过程,似乎并不复杂。不过事情还没完,我们需要继续 "了解" 父进程在创建子进程之后做了什么。

回到 Process.start(),Popen 创建并调用子进程,而父进程则继续执行下一步。


class Process(object):

def start(self):
...

if self._Popen is not None:
Popen = self._Popen
else:
from .forking import Popen

self._popen = Popen(self)
_current_process._children.add(self)


这个 _current_process 默认应该代表父进程,只是从何而来?在 process.py 最后面有这些代码。


class _MainProcess(Process):

def __init__(self):
self._identity = ()
self._daemonic = False
self._name = 'MainProcess'
self._parent_pid = None
self._popen = None
self._counter = itertools.count(1)
self._children = set()
self._authkey = AuthenticationString(os.urandom(32))
self._tempdir = None

_current_process = _MainProcess()
del _MainProcess


只是对当前进程(父进程)的简单包装。父进程通过 Process.start() 创建子进程,并添加到一个子进程列表(_children)中。那么父进程如何处理子进程的退出呢?要知道不小心会搞出很多僵尸进程(参见 《fork: 杀僵尸练级》)。

在 multiprocessing.__init__.py 中引入了 util。


from multiprocessing.util import SUBDEBUG, SUBWARNING


在 util.py 尾部有这些代码。


from multiprocessing.process import current_process, active_children

def _exit_function():
global _exiting

info('process shutting down')
debug('running all "atexit" finalizers with priority >= 0')
_run_finalizers(0)

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()

debug('running the remaining "atexit" finalizers')
_run_finalizers()

atexit.register(_exit_function)


active_children 由 process 导入。


def current_process():
'''Return process object representing the current process'''
return _current_process

def active_children():
'''Return list of process objects corresponding to live child processes'''
_cleanup()
return list(_current_process._children)


_exit_function 会在父进程(或子进程,子进程可以创建自己的子进程)退出时检查其子进程列表,并调用 join 等待子进程退出。如果子进程是 daemon,则直接调用 terminate (发送 SIGTERM 信号) 终止。


class Process(object):

def join(self, timeout=None):
...
res = self._popen.wait(timeout)
if res is not None:
_current_process._children.discard(self)

class Popen(object):

def wait(self, timeout=None):
if timeout is None:
return self.poll(0)
...
while 1:
res = self.poll()
...
return res

def poll(self, flag=os.WNOHANG):
if self.returncode is None:
pid, sts = os.waitpid(self.pid, flag)
if pid == self.pid:
if os.WIFSIGNALED(sts):
self.returncode = -os.WTERMSIG(sts)
else:
assert os.WIFEXITED(sts)
self.returncode = os.WEXITSTATUS(sts)
return self.returncode

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


Process.join() 调用 Popen.wait(),而 wait() 内部则通过 poll() 调用 os.waitpid() 等待子进程终止,如此这般才算完成了一个完整的流程。

附注: 子进程同样也会在其退出时调用 uitl._exit_function。

------------ 分隔线 -----------------

本文主要分析 multiprocessing 的相关原理,有关使用细节可参考标准库帮助文件。


...全文
253 5 打赏 收藏 转发到动态 举报
写回复
用AI写文章
5 条回复
切换为时间正序
请发表友善的回复…
发表回复
zxhyl 2010-07-11
  • 打赏
  • 举报
回复
您好!
我想买您的书!深入WINDOWS编程 这本书!
请联系我!
QQ1197474711 zxhyl@126.com
hywhy 2010-06-20
  • 打赏
  • 举报
回复
正好,兄弟,请教一个问题,我下面的代码,为什么执行回报错呢,谢谢
from multiprocessing.managers import BaseManager
import Queue

class CrawlerManager(BaseManager):
pass

downloader_queue = Queue.Queue()

downloader_queue.put('hello')

CrawlerManager.register('get_downloader_queue', callable=lambda: downloader_queue)

mgr = CrawlerManager()
mgr.start()

q = mgr.get_downloader_queue()

print q.get()
liulifan 2010-06-20
  • 打赏
  • 举报
回复
支持。学习源码实属王道。能静下来学一下,真是不错。
lpsw1 2010-06-20
  • 打赏
  • 举报
回复
xiexiefenxiang,以后有机会也要如此
DarkChampion 2010-06-20
  • 打赏
  • 举报
回复
不错,支持一下

37,744

社区成员

发帖
与我相关
我的任务
社区描述
JavaScript,VBScript,AngleScript,ActionScript,Shell,Perl,Ruby,Lua,Tcl,Scala,MaxScript 等脚本语言交流。
社区管理员
  • 脚本语言(Perl/Python)社区
  • WuKongSecurity@BOB
加入社区
  • 近7日
  • 近30日
  • 至今

试试用AI创作助手写篇文章吧