1. 文档是最先需要了解的,读完文档可能会有很多的意外的收获同时也会留下疑惑,对于一般的使用我觉得读完文档就差不多了,除非一些很有疑惑的地方你可能需要再深入的了解一下。我读文档的目的第一个就是为了找出疑惑然后带着疑惑去读源码,还有一个目的就是为了后面读源码提供指导。
2. multiprocessing.Process类是multiprocessing中最基础的类,后面的pool都是在这个类的基础上再包装的,所以从它入手最合适不过了。这个类的文档和源码都比较简单,唯一可能有点让人疑惑的就是daemon这个属性,千万不要被名字迷惑了,它跟我们常见的Linux daemon进程没有任何关系了。简单点说它的功能就是:一个进程退出(exit)的时候会尝试把它所有daemonic(设置了daemon属性)的子进程终止(terminate)。
3. multiprocessing.Process源码分析
当我们import multiprocessing的时候就会生成一个_MainProcess类,这是代表当前主进程,内容比较简单。当我们在主进程中调用Process()来生成一个进程对象的时候会从_MainProcess中继承一些属性,例如:daemon属性。当调用Process.start()开始运行一个进程的时候首先会有三条assert断言语句:不能重复start一个进程、只能start当前进程自己创建的进程、daemon进程不能有子进程。接着调用_cleanup()清理当前已经运行完的进程(join,然后把它从当前进程的子进程集合中去掉)。到了动真格的时候,就是要fork生成子进程了,在Process中用Popen封装了子进程的操作,用属性_popen来代表子进程。下面是Process.start()的源码:

def start(self):
'''
Start child process
'''
assert self._popen is None, 'cannot start a process twice'
assert self._parent_pid == os.getpid(), \
'can only start a process object created by current process'
assert not _current_process._daemonic, \
'daemonic processes are not allowed to have children'
_cleanup()
if self._Popen is not None:
Popen = self._Popen
else:
from .forking import Popen
self._popen = Popen(self)
_current_process._children.add(self)

  

我们看到调用Popen创建子进程,然后把生成的Process对象加入到当前进程的子进程集合中后我们就算完成了子进程的初始化、运行,这个步骤看起来比较简单,其实真正的干货实在Popen中,所以我们接着来看forking.Popen类。
4. multiprocessing.forking.Popen源码分析
Popen首先把stdin、stdout刷新一下,接着就是代表子进程退出码的returncode,然后就是调用os.fork来真正生成子进程。fork后当前进程(也就是父进程)返回并且记录子进程的进程ID(Popen对象的pid属性),新生成的子进程调用Process对象的_bootstrap()来运行我们的target,最后调用os._exit()直接退出。

Process.start()->Popen.__init__()->os.fork->Process._bootstrap()

  

我们又回到了Process了,感觉是不是有点绕呢。当我们再次回到Process.__bootstrap()的时候一定要记住我们是在一个克隆的新空间中了,不再是前文中我们一直提到的当前进程了,而是我们在当前进程新生成的子进程中了。在__bootstrap()中会先重新设置children、_count属性以及代表当前进程的全局变量_current_process,之所以要重新设置就是我们已经新生成了一个进程空间,我们当前是在这个新空间中运行着,特别是代表当前进程的_current_process变量需要好好理解一下。接着就是清空_finalizer_registry(这个我们留到后面再说),调用util._run_after_fork()来做一些fork之后运行target之前的操作,当然默认情况下是没有任何操作的,不过我们也可以调用util.register_after_fork()来注册一些函数,util.register_after_fork()这个函数在文档中是没有提及到的,算是一个”偏门”。最后就是调用Process.run运行我们的target了,到此时我们的子进程就运行到了我们自己要运行的目标中了。运行完后会调用util._exit_function()来做一些扫尾工作,这个函数是我们接下来的重点内容。

5. 进程退出的清理工作,_exit_function源码分析
无论是主进程还是生成的子进程在进程退出之前都需要做一些清理工作,避免一些系统资源被一直占用而不释放。在主进程中当我们import multiprocessing.forking的时候会间接的import multiprocessing.util,此时就会在util中调用atexit.register(_exit_function)来注册主进程的exit handler。而子进程在上面我们看到运行完我们的target以后会主动调用_exit_function(),所以无论是主进程还是子进程都是调用_exit_function()来做最后的”善后”工作。
_exit_function()的功能其实也比较简单,调用_run_finalizers(0)、终止(terminate)daemonic子进程、join回收子进程、再次调用_run_finalizers()(参数有变化)。这里的代码也很好的帮我理解了进程的daemon属性是干嘛的了。

def _exit_function(info=info, debug=debug, _run_finalizers=_run_finalizers,
active_children=active_children,
current_process=current_process):
# NB: we hold on to references to functions in the arglist due to the
# situation described below, where this function is called after this
# module's globals are destroyed. global _exiting info('process shutting down')
debug('running all "atexit" finalizers with priority >= 0')
_run_finalizers(0) if current_process() is not None:
# NB: we check if the current process is None here because if
# it's None, any call to ``active_children()`` will throw an
# AttributeError (active_children winds up trying to get
# attributes from util._current_process). This happens in a
# variety of shutdown circumstances that are not well-understood
# because module-scope variables are not apparently supposed to
# be destroyed until after this function is called. However,
# they are indeed destroyed before this function is called. See
# issues 9775 and 15881. Also related: 4106, 9205, and 9207. 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()

  

接下来就是_run_finalizer()了,这个函数就是把优先级高于传入的minpriority参数的finalizer函数按照创建时间的倒序执行一遍,当前进程的finalizer函数都是保存在全局变量_finalizer_registry中,这个默认是为空的,也就是没有任何的额外finalizer函数会调用,不过用户可以通过创建multiprocessing.util.Finalize对象来注册finalizer函数。Finalize这个类在multiprocessing的文档中也没有公开,所以这也算是一个隐藏的功能。这个类我们可以在什么时候用到呢?比如你在一个Process中创建了一个数据库连接,需要在进程退出之前关闭这个连接,你除了在你的target代码中执行关闭以外还可以通过实例化multiprocessing.util.Finalize对象来实现这个功能。在multiprocessing内部代码中就有很多的地方使用Finalize来实现清理工作。

6. 至此Process的出生到灭亡我们都有涉及了,其它的一些功能大体从代码中都能看明白了,无非就是对子进程的pid做一些os.waitpid、os.kill之类的调用,不再一一讨论了。Process只是multiprocessing中最基础的部分,后面我会接着写一下最重要的multiprocessing.pool。

python multiprocessing 源码分析的更多相关文章

  1. eos源码分析和应用(一)调试环境搭建

    转载自 http://www.limerence2017.com/2018/09/02/eos1/#more eos基于区块链技术实现的开源引擎,开发人员可以基于该引擎开发DAPP(分布式应用).下面 ...

  2. python slots源码分析

    上次总结Python3的字典实现后的某一天,突然开窍Python的__slots__的实现应该也是类似,于是翻了翻CPython的源码,果然如此! 关于在自定义类里面添加__slots__的效果,网上 ...

  3. Python SocketServer源码分析

    1      XXXServer 1.1      BaseSever 提供基础的循环等待请求的处理框架.使用serve_forever启动服务,使用shutdown停止.同时提供了一些可自行扩展的方 ...

  4. python SocketServer 源码分析

    附上原文链接: http://beginman.cn/python/2015/04/06/python-SocketServer/

  5. selenium3 + python - action_chains源码分析

    ActionChains简介 actionchains是selenium里面专门处理鼠标相关的操作如:鼠标移动,鼠标按钮操作,按键和上下文菜单(鼠标右键)交互.这对于做更复杂的动作非常有用,比如悬停和 ...

  6. python ironicclient源码分析

    ironicclient是一个cli工具,用来和用户交互的. 首先写一个简单的例子,获取ironic所有的node节点: from ironicclient import client if __na ...

  7. Python之美[从菜鸟到高手]--urlparse源码分析

    urlparse是用来解析url格式的,url格式如下:protocol :// hostname[:port] / path / [;parameters][?query]#fragment,其中; ...

  8. [python] 基于词云的关键词提取:wordcloud的使用、源码分析、中文词云生成和代码重写

    1. 词云简介 词云,又称文字云.标签云,是对文本数据中出现频率较高的“关键词”在视觉上的突出呈现,形成关键词的渲染形成类似云一样的彩色图片,从而一眼就可以领略文本数据的主要表达意思.常见于博客.微博 ...

  9. Python源码分析(二) - List对象

    python中的高级特性之一就是内置了list,dict等.今天就先围绕列表(List)进行源码分析. Python中的List对象(PyListObject) Python中的的PyListObje ...

随机推荐

  1. Random类、ThreadLocalRandom类

    Random和ThreadLocalRandom类均用于生成伪随机数. Random的构造函数: Random()     默认以系统当前时间为种子,相当于Random(System.currentT ...

  2. Ionic 2 中的创建一个闪视卡片组件

    闪视卡片是记忆信息的重要工具,它的使用可以追溯到19世纪.我们将要创建一个很酷的短暂动画来实现它.看起来像是这个样子的: 闪视卡片示例 Ionic 2 实例开发 新增章节将为你介绍如何在Ionic 2 ...

  3. Codeforces Round #411 div2

    A. Fake NP 题意:询问一个区间[L,R]出现次数最多的正整数因子(>1). 一个区间内一个因子P出现次数大概为[R/P]-[(L-1)/P],约等于(R-L+1)/P,P取2时最优.注 ...

  4. 如何对ABAP SE80 workbench做增强

    流程如下: 要获取更多Jerry的原创文章,请关注公众号"汪子熙":

  5. 数据分析R&Python-Rpy2包环境配置

    Rpy2环境配置 最近想将R整合到以flask为后端框架的web系统中,在服务器端做数据统计分析.需要将R语言整合到Python中,发现Python中的Rpy2可以调用R语言,所以花了一些时间配置了一 ...

  6. git 作成

    Git global setup git config --global user.name "高 文龍" git config --global user.email " ...

  7. Sql Server 查询今天,昨天,近七天....数据

    今天数据: 昨天数据: 7天内数据: 30天内数据: 本月数据: 本年数据: 查询今天是今年的第几天: select datepart(dayofyear,getDate()) 查询今天是本月的第几天 ...

  8. bzoj4666 小z的胡话

    题目描述: bz 题解: 乱搞好题哇. 众所周知斐波那契数列是有循环节的. 我们可以搞出在模$10^x$下与所给得数同余的集合,那么在模$10^{x+1}$下,同余集合一定是原集合及循环若干循环节的大 ...

  9. STL 之 sort 函数使用方法

    关于Sort Sort函数是C++ STL(Standard Template Library / 标准函数库) <algorithm>头文件中的一个排序函数,作用是将一系列数进行排序,因 ...

  10. 转 Solr vs. Elasticsearch谁是开源搜索引擎王者

    转 https://www.cnblogs.com/xiaoqi/p/6545314.html Solr vs. Elasticsearch谁是开源搜索引擎王者 当前是云计算和数据快速增长的时代,今天 ...