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()的源码:

  1. def start(self):
  2. '''
  3. Start child process
  4. '''
  5. assert self._popen is None, 'cannot start a process twice'
  6. assert self._parent_pid == os.getpid(), \
  7. 'can only start a process object created by current process'
  8. assert not _current_process._daemonic, \
  9. 'daemonic processes are not allowed to have children'
  10. _cleanup()
  11. if self._Popen is not None:
  12. Popen = self._Popen
  13. else:
  14. from .forking import Popen
  15. self._popen = Popen(self)
  16. _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()直接退出。

  1. 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属性是干嘛的了。

  1. def _exit_function(info=info, debug=debug, _run_finalizers=_run_finalizers,
  2. active_children=active_children,
  3. current_process=current_process):
  4. # NB: we hold on to references to functions in the arglist due to the
  5. # situation described below, where this function is called after this
  6. # module's globals are destroyed.
  7.  
  8. global _exiting
  9.  
  10. info('process shutting down')
  11. debug('running all "atexit" finalizers with priority >= 0')
  12. _run_finalizers(0)
  13.  
  14. if current_process() is not None:
  15. # NB: we check if the current process is None here because if
  16. # it's None, any call to ``active_children()`` will throw an
  17. # AttributeError (active_children winds up trying to get
  18. # attributes from util._current_process). This happens in a
  19. # variety of shutdown circumstances that are not well-understood
  20. # because module-scope variables are not apparently supposed to
  21. # be destroyed until after this function is called. However,
  22. # they are indeed destroyed before this function is called. See
  23. # issues 9775 and 15881. Also related: 4106, 9205, and 9207.
  24.  
  25. for p in active_children():
  26. if p._daemonic:
  27. info('calling terminate() for daemon %s', p.name)
  28. p._popen.terminate()
  29.  
  30. for p in active_children():
  31. info('calling join() for process %s', p.name)
  32. p.join()
  33.  
  34. debug('running the remaining "atexit" finalizers')
  35. _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. DatabaseMetaData类

    DatabaseMetaData类是java.sql包中的类,利用它可以获取我们连接到的数据库的结构.存储等很多信息.如: 1.数据库与用户,数据库标识符以及函数与存储过程.         2.数据 ...

  2. ThreadLocal(关于struts2的ThreadLocal,实际上Jdk1.2就有了)

    ThreadLocal是通过在不同线程中操作变量的副本,来达到线程安全的目的,是用空间资源换时间资源的方式.今天在看struts2源码的时候,发现ActionContext中,就持有一个静态的Thre ...

  3. mysql 5.7.20 在线安装与卸载(yum卸载与rpm卸载方式)

    mysql5.7.20和之前的5.7.16版本不同,解压后没有data文件,需要自己建立 1.把下载的mysql5.7.20放到目录:/usr/local/2.卸载cenos上预装的mysql查看已安 ...

  4. Git随笔:尝试将本地工程上传至Github上的repository仓库,构建远端与本地协同的Git环境

    上传工程至自己的Github公开库,步骤如下: 第1步:建立本地 git 仓库,cd 到你的本地项目根目录下,执行 git init 命令: 第2步:将本地项目工作区的所有文件添加到暂存区.小数点 & ...

  5. URAL 1057 Amount of Degrees (数位DP,入门)

    题意: 求给定区间[X,Y]中满足下列条件的整数个数:这个数恰好等于K个互不相等的,B的整数次幂之和.例如,设X=15,Y=20,K=2,B=2,则有且仅有下列三个数满足了要求:  17 = 24+2 ...

  6. pod install Pull is not possible because you have unmerged files.

    http://stackoverflow.com/questions/21474536/podfile-gives-an-error-on-install A bug was found in lib ...

  7. 2012-2013 ACM-ICPC, NEERC, Central Subregional Contest J Computer Network1 (缩点+最远点对)

    题意:在连通图中,求一条边使得加入这条边以后的消除的桥尽量多. 在同一个边双连通分量内加边肯定不会消除桥的, 求边双连通分量以后缩点,把桥当成边,实际上是要选一条最长的链. 缩点以后会形成一颗树,一定 ...

  8. 2017年团体程序设计天梯赛 - 大区赛 L3-3

    题意:有向图找哈密顿回路 比赛的时候剪枝只剪了vis 状压没剪对 反而只拿17分... 比赛结束后还去看了一发这个NP问题的QB(快速回溯法...但是对于本题好像大材小用...) 上网看了一个神犇的写 ...

  9. 删除Chrome地址栏记录中自动补全的网址

    为了删除某个自动补全的网站,多年的历史纪录没了,还浪费我十多分钟,蠢哭_(:з」∠)_ 不是历史记录.不是清除浏览器数据.不是myactivity(谷歌账号)中的历史纪录,直接在书签中搜索,删除,OK ...

  10. linux基本命令及使用方法

    shell环境: shell:命令解释器,是Linux 系统的用户界面,提供了用户与内核进行交互操作的一种接口,它接收用户输入的命令并把它送入内核去执行. bash:是GNU 计划中重要的工具软件之一 ...