前言:
最近帮朋友review其模块服务代码, 使用的是python的twisted网络框架. 鉴于之前并没有使用过, 于是决定好好研究一番.
不过这个问题, 和twisted网络框架本身没有关系, 而是为了提升QPS性能, 引入了多进程模型而遇到的一个奇怪问题.
本文将讲述python的多进程机制, 以及atexit的清理函数机制. 权作python的学习笔记.
场景构造:
具体的业务代码可以简化为如下所述:
父进程开启多个常驻子进程后, 安然"退休"并结束运行.
进程的创建借助multiprocessing模块来实现, 其使用方式如thread管理接口相仿.
编辑python代码文件 subproc_sample.py:
#! /usr/bin/python #-*- coding: UTF-8 -*- import time import multiprocessing # 子进程业务逻辑 def proc_func(): while True: time.sleep(1) if __name__ == "__main__": # 创建4个子进程 for i in range(4): # 创建子进程并启动 p = multiprocessing.Process(target=proc_func, args=()) p.start() # 父进程安然退出
该样例中, 父进程创建4个子进程后, 就Do nothing, 安然退出了(预期中).
然后事实却不如此, 我们借助 ps 命令过滤后发现, 父进程和4个子进程都活着, 如下图所示:
这中间有何玄机呢? 父进程为何不按常理出牌呢?
问题追踪:
"工欲善其事, 必先利其器", 我们借助pstack来查看下父进程(python底层是用c/c++编写)为何不退出.
命令: pstack <pid>, pid为父进程id.
从堆栈的顶层可以看到, 父进程阻塞在waitpid系统调用. 看来父进程在等待子进程的正常退出.
令一方面, 从堆栈中, 我们可以发现, 此时父进程已在python主程序退出之后, 其做了清理工作Py_Finalize.
这让我们联想到unix c中提到atexit退出清理机制.
python是否也有类似的机制, 可以注册程序的退出清理函数.
我们把父进程切换到前台运行, fg之后, ctrl+c挂掉父进程.
从中可以看到atexit.py, 由此可见, 我们的猜测并没有错.
在/usr/lib64/python2.6/multiprocessing/util.py中, 我们可以找到注册的时间点, 还有注册的函数_exit_function, 该函数完成了父进程对子进程的wait操作.
总结:
python的多线程设计的非常的巧妙, 使得创建/使用多进程,如同多线程使用那样方便和简洁. 当然python背后也封装添加了一些额外的工作, 这些工作可能会有悖于Unix开发者的常规想法. 当然任何诡异的现象, 背后一定有迹可循.
写在最后:
如果你觉得这篇文章对你有帮助, 请小小打赏下. 其实我想试试, 看看写博客能否给自己带来一点小小的收益. 无论多少, 都是对楼主一种由衷的肯定.