"竹杖芒鞋轻胜马, 一蓑烟雨任平生"

Do not deify machine learning,it is not that cool

一次多进程冲突引发僵尸(defunct)进程的排错经历

10 May 2013
背景

公司某个服务迁移到另一台服务器上,迁移过程中部分数据要进行恢复。属于日志类数据,按天分割。于是乎改造了一下入库脚本为多进程模式,按照每天起一个进程的节奏来恢复。
表象:
一般脚本起来后我都习惯性的ps一下看看脚本是否正常运行。当在启动多进程恢复脚本之后,随手一ps,结果发现进程列表出现一坨Z+的defunct进程,也就是僵尸进程,当时我整个人就思密达了。我们知道僵尸进程出现的原因是在父进程没对自己fork出来的子进程进行善后处理的情况下,子进程先于父进程一步挂了(白发人送黑发人,此时子进程就成了僵尸进程,除非父进程挂掉,此时init进程接管这些僵尸进程,替父进程进行善后的清理)。而对于我的脚本,子进程绝逼不会比父进程结束的早,因为父进程只是按照日期个数挨个起一个进程然后就退出,而子进程要进行日志分析处理等一系列复杂操作,并且每天日志文件数量在6k左右。代码如下:

if name == “main”:
    dates = []
    InitDate(dates)
    for i in dates:
      p = multiprocessing.Process(target=test.run, args=(i, ))
      p.start()
      #comment it to detect zombine process
      #signal.signal(signal.SIGCHLD, signal.SIG_IGN)

其中dates是一个日期的列表,用膝盖想也是父进程应该先退出。看了一下脚本运行log,发现提前结束的子进程获取到的文件路径信息很诡异,不是完整路径信息。既然和预期不符,那就是代码的问题了。
解决过程:
首先我把主要的逻辑代码单独抠出来进行测试,成功复现这一问题,于是怀疑是多进程造成并发冲突,因为诡异的结果往往是内存冲突或者进程冲突造成的。我在可能冲突的地方放一个sleep函数,每个进程随机sleep个几秒。嗯,结果正常了,进一步排查,发现是test文件中有一个全局的变量,脑残了。到这就豁然开朗了,应该是全局变量出现并发访问,导致A进程的结果被B进程给拿到,B进程的结果不知道被谁拿到等等。
结果:
把全局变量改为局部变量,然后作为参数传递给用到的函数,这样保证了此变量只存在每个进程的堆栈空间中,防止出现并发冲突。然后实验一下,嗯,妥妥好使。

comments powered by Disqus