背景

一次艰辛的python代码调试。 poen函数,先后遇到 子进程kill不掉执行任务text file busy(让我一个英语盲, 竟然能这么熟练的敲出这三个单词,一定是经历了什么不为人知的···)。 简单说明一下程序的作用, 从网络接收脚本, 然后执行脚本, 最后收集脚本执行的结果。嗯,就是这样简单。 网络服务采用的是多线程模式,子进程采用 popen方式打开,输出使用管道。 时间紧, 直接跳转到最后看总结即可。

问题一: pipe管道堵塞

我们采用管道方式收集输出信息,使用 poll 方法检测是否结束。 好的, 问题来了。 当子进行一次输出较大日志量的时候会堵死管道, 子进程阻塞。 我们需要使用超时等机制, python2.7 的 communicate 方法没有 超时选项。 因此采用了 threading模块的 的 timer 设置超时, 主服务读取管道,保证不会是管道堵死。 嗯,这样就好了,完美。

问题二: 子进程kill不了

等等,这个我超时设置的5S,怎么10S了还不返回!看看,转态变了,kill 执行了,为什么还不返回? 找到好像是shell参数设置为True, 多启动了一层进行,而我们调用的命令会生成更多的子进程,虽然kill了一个,但是它的子子孙孙一堆都还在, 管道也没有关闭,主服务线程依然还在读管道,不会停止。 因此, 需要找能够kill子子孙孙的办法。 最后在 stackoverflow找到了高票的回复,可以kill process group。 实例代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import os
import signal
import subprocess

# The os.setsid() is passed in the argument preexec_fn so
# it's run after the fork() and before  exec() to run the shell.
pro = subprocess.Popen(cmd, stdout=subprocess.PIPE,
                       shell=True, preexec_fn=os.setsid)

os.killpg(os.getpgid(pro.pid), signal.SIGTERM)

嗯,按着这个测试,哦哦,可以kill了。 果然是 stackoverflow ,编程问题来这里一定可以。 嗯,这样就好了,完美。

问题三: Text file busy

上线了, 终于可以休息了。 电话来了:你们是不是做了什么改动啊,我的脚本怎么都出错了,原来任务好好的!看看,看看。 哦,报 text file busy。 还不是所有的任务都报,偶现。 网上都说,你这个是 文件有写打开,没有关闭。 我检查我的代码,都是好的啊, 我用的 with open 打开的, 没有关闭,怎么会呢? 代码一行行的检查中。 明显不是我的代码的问题,肯定是你们的问题。 自己偷偷的检查代码,python文档close_fds 这个参数是干什么的? 这个是在启动这个是在 启动子进程的时候除了 0、1、2三个文件描述符会继续打开外,其他的都会关闭。 如果不加,在popen的时候,创建子进程, 集成了正在接收文件的文件描述符,若后面该文件需要执行的时候,现在的子进程没有结束,这个字写文件描述符是不会自动关闭的。因此,就出现了 Text file busy。 修改完之后还是有些特殊的任务还是会报 Text file busy, 最后,修改 网络server 使用多进程方式进行服务。 每个请求来了之后创建自己的进程,各个子进程后续打开的文件不会相互影响。 想起了不知道在哪看到的,不要在多线程中打开fork进程, 可能会出现一些莫名其妙的问题, 谨记。

问题四: 性能问题

在切换使用多进程的时候就有担心新能问题,首先在多进程的情况下, ThreadingMixIn 是没有限定最大的线程数量, 但是 ForkingMixIn 限定了 max_children = 40, timeout = 300。 因此,多进程默认情况下面会限定同事处理的个数是40个。 另外一个疑问是, 创建进程的开销会比创建线程的开销大。

总结

  1. python popen 若使用 管道方式接收进行交互处理,注意处理管道文件,不要阻塞管道
  2. popen kill 不能正常工作的时候,看一下是不是启动了子进程,若是的,可以加上preexec_fn=os.setsid参数。
  3. popen的时候主进程若有文件处于打开状态,会被子进程继承,注意添加close_fds=True参数。当然,能从根本上避免是更好的。