python僵尸进程产生的原因


Posted in Python onJuly 21, 2017

在 unix 或 unix-like 的系统中,当一个子进程退出后,它就会变成一个僵尸进程,如果父进程没有通过 wait 系统调用来读取这个子进程的退出状态的话,这个子进程就会一直维持僵尸进程状态。

Zombie process - Wikipedia 中是这样描述的:

On Unix and Unix-like computer operating systems, a zombie process or defunct process is a process that has completed execution (via the exit system call) but still has an entry in the process table: it is a process in the "Terminated state". This occurs for child processes, where the entry is still needed to allow the parent process to read its child's exit status: once the exit status is read via the wait system call, the zombie's entry is removed from the process table and it is said to be "reaped". A child process always first becomes a zombie before being removed from the resource table. In most cases, under normal system operation zombies are immediately waited on by their parent and then reaped by the system ? processes that stay zombies for a long time are generally an error and cause a resource leak.

并且僵尸进程无法通过 kill 命令来清除。

本文将探讨如何手动制造一个僵尸进程以及清除僵尸进程的办法。

手动制造一个僵尸进程

为了便于后面讲解清除僵尸进程的方法,我们使用日常开发中经常使用的 multiprocessing 模块来制造僵尸进程(准确的来说是制造一个长时间维持僵尸进程状态的子进程):

$ cat test_a.py
from multiprocessing import Process, current_process
import logging
import os
import time

logging.basicConfig(
  level=logging.DEBUG,
  format='%(asctime)-15s - %(levelname)s - %(message)s'
)


def run():
  logging.info('exit child process %s', current_process().pid)
  os._exit(3)

p = Process(target=run)
p.start()
time.sleep(100)

测试:

$ python test_a.py &
[1] 10091
$ 2017-07-20 21:28:14,792 - INFO - exit child process 10106

$ ps aux |grep 10106
mozillazg       10126  0.0 0.0 2434836  740 s006 R+  0:00.00 grep 10106
mozillazg       10106  0.0 0.0    0   0 s006 Z   0:00.00 (Python)

可以看到,子进程 10091 变成了僵尸进程。

既然已经可以控制僵尸进程的产生了,那我们就可以进入下一步如何清除僵尸进程了。

清除僵尸进程有两种方法:

•第一种方法就是结束父进程。当父进程退出的时候僵尸进程随后也会被清除。
• 第二种方法就是通过 wait 调用来读取子进程退出状态。我们可以通过处理 SIGCHLD 信号,在处理程序中调用 wait 系统调用来清除僵尸进程。

处理 SIGCHLD 信号

子进程退出时系统会向父进程发送 SIGCHLD 信号,父进程可以通过注册 SIGCHLD 信号处理程序,在信号处理程序中调用 wait
系统调用来清理僵尸进程。 $ cat test_b.py

import errno
from multiprocessing import Process, current_process
import logging
import os
import signal
import time

logging.basicConfig(
  level=logging.DEBUG,
  format='%(asctime)-15s - %(levelname)s - %(message)s'
)


def run():
  exitcode = 3
  logging.info('exit child process %s with exitcode %s',
         current_process().pid, exitcode)
  os._exit(exitcode)


def wait_child(signum, frame):
  logging.info('receive SIGCHLD')
  try:
    while True:
      # -1 表示任意子进程
      # os.WNOHANG 表示如果没有可用的需要 wait 退出状态的子进程,立即返回不阻塞
      cpid, status = os.waitpid(-1, os.WNOHANG)
      if cpid == 0:
        logging.info('no child process was immediately available')
        break
      exitcode = status >> 8
      logging.info('child process %s exit with exitcode %s', cpid, exitcode)
  except OSError as e:
    if e.errno == errno.ECHILD:
      logging.error('current process has no existing unwaited-for child processes.')
    else:
      raise
  logging.info('handle SIGCHLD end')

signal.signal(signal.SIGCHLD, wait_child)

p = Process(target=run)
p.start()

while True:
  time.sleep(100)

效果:

$ python test_b.py &
[1] 10159
$ 2017-07-20 21:28:56,085 - INFO - exit child process 10174 with exitcode 3
2017-07-20 21:28:56,088 - INFO - receive SIGCHLD
2017-07-20 21:28:56,089 - INFO - child process 10174 exit with exitcode 3
2017-07-20 21:28:56,090 - ERROR - current process has no existing unwaited-for child processes.
2017-07-20 21:28:56,090 - INFO - handle SIGCHLD end

$ ps aux |grep 10174
mozillazg       10194  0.0 0.0 2432788  556 s006 R+  0:00.00 grep 10174

可以看到,子进程退出变成僵尸进程后,系统给父进程发送了 SIGCHLD 信号,我们在 SIGCHLD 信号的处理程序中通过 os.waitpid 调用 wait 系统调用后阻止了子进程一直处于僵尸进程状态,从而实现了清除僵尸进程的效果。

Python 相关文章推荐
详解Python进程间通信之命名管道
Aug 28 Python
利用ctypes获取numpy数组的指针方法
Feb 12 Python
Python3爬楼梯算法示例
Mar 04 Python
CentOS7安装Python3的教程详解
Apr 10 Python
详解python 中in 的 用法
Dec 12 Python
python对文件的操作方法汇总
Feb 28 Python
Python2手动安装更新pip过程实例解析
Jul 16 Python
零基础学Python之前需要学c语言吗
Jul 21 Python
PyQt5结合matplotlib绘图的实现示例
Sep 15 Python
在终端启动Python时报错的解决方案
Nov 20 Python
python glom模块的使用简介
Apr 13 Python
Python实现归一化算法详情
Mar 18 Python
python下载图片实现方法(超简单)
Jul 21 #Python
Python基于Pymssql模块实现连接SQL Server数据库的方法详解
Jul 20 #Python
Python使用内置json模块解析json格式数据的方法
Jul 20 #Python
Python轻量级ORM框架Peewee访问sqlite数据库的方法详解
Jul 20 #Python
Python函数式编程
Jul 20 #Python
python 换位密码算法的实例详解
Jul 19 #Python
python实现rsa加密实例详解
Jul 19 #Python
You might like
PHP 程序授权验证开发思路
2009/07/09 PHP
在IE下:float属性会影响offsetTop的取值
2006/12/22 Javascript
jquery 常用操作整理 基础入门篇
2009/10/14 Javascript
jquery关于图形报表的运用实现代码
2011/01/06 Javascript
DOM2非标准但却支持很好的几个属性小结
2012/01/21 Javascript
js获取某月的最后一天日期的简单实例
2013/06/22 Javascript
JavaScript截取字符串的Slice、Substring、Substr函数详解和比较
2014/03/20 Javascript
NODE.JS加密模块CRYPTO常用方法介绍
2014/06/05 Javascript
JavaScript实现数字数组按照倒序排列的方法
2015/04/06 Javascript
JavaScript获取表格(table)当前行的值、删除行、增加行
2015/07/03 Javascript
全面了解addEventListener和on的区别
2016/07/14 Javascript
浅谈Javascript数据属性与访问器属性
2016/07/26 Javascript
原生js jquery ajax请求以及jsonp的调用方法
2017/08/04 jQuery
前端跨域的几种解决方式总结(推荐)
2017/08/16 Javascript
vue.js或js实现中文A-Z排序的方法
2018/03/08 Javascript
详解使用Next.js构建服务端渲染应用
2018/07/10 Javascript
Vue+elementUI实现多图片上传与回显功能(含回显后继续上传或删除)
2020/03/23 Javascript
在antd Form表单中select设置初始值操作
2020/11/02 Javascript
python验证码识别实例代码
2018/02/03 Python
Python中enumerate()函数编写更Pythonic的循环
2018/03/06 Python
PyQt5每天必学之关闭窗口
2018/04/19 Python
Tensorflow模型实现预测或识别单张图片
2019/07/19 Python
python @propert装饰器使用方法原理解析
2019/12/25 Python
Python切割图片成九宫格的示例代码
2020/03/10 Python
Python logging模块写入中文出现乱码
2020/05/21 Python
pycharm实现print输出保存到txt文件
2020/06/01 Python
Python3+RIDE+RobotFramework自动化测试框架搭建过程详解
2020/09/23 Python
Python对excel的基本操作方法
2021/02/18 Python
职工运动会感言
2014/02/07 职场文书
逃课上网检讨书
2014/02/20 职场文书
2014年政风行风自查自纠报告
2014/10/21 职场文书
2015年安全生产月活动总结
2015/03/26 职场文书
2015年公务员个人工作总结
2015/04/24 职场文书
2019年员工旷工保证书!
2019/06/28 职场文书
为什么 Nginx 比 Apache 更牛逼
2021/03/31 Servers
手把手教你使用TensorFlow2实现RNN
2021/07/15 Python