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中is和id的用法
Apr 03 Python
详解Python中contextlib上下文管理模块的用法
Jun 28 Python
idea创建springMVC框架和配置小文件的教程图解
Sep 18 Python
在Pycharm中修改文件默认打开方式的方法
Jan 17 Python
python里 super类的工作原理详解
Jun 19 Python
python利用tkinter实现屏保
Jul 30 Python
python读写csv文件的方法
Aug 13 Python
Django中间件拦截未登录url实例详解
Sep 03 Python
PyQt5 closeEvent关闭事件退出提示框原理解析
Jan 08 Python
Python实现Wordcloud生成词云图的示例
Mar 30 Python
浅谈Keras中shuffle和validation_split的顺序
Jun 19 Python
Python中使用tkFileDialog实现文件选择、保存和路径选择
May 20 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 在线打包_支持子目录
2008/06/28 PHP
PHP 前加at符合@的作用解析
2015/07/31 PHP
php创建桌面快捷方式实现方法
2015/12/31 PHP
php获取指定数量随机字符串的方法
2017/02/06 PHP
PHP对象的浅复制与深复制的实例详解
2017/10/26 PHP
tp5.1 框架join方法用法实例分析
2020/05/26 PHP
一种JavaScript的设计模式
2006/11/22 Javascript
JS前端框架关于重构的失败经验分享
2013/03/17 Javascript
javaScript函数中执行C#代码中的函数方法总结
2013/08/07 Javascript
在页面中js获取光标/鼠标的坐标及光标的像素坐标
2013/11/11 Javascript
js获取url参数代码实例分享(JS操作URL)
2013/12/13 Javascript
javascript:FF/Chrome与IE动态加载元素的区别说明
2014/01/26 Javascript
javascript判断office版本示例
2014/04/11 Javascript
JavaScript实现大数的运算
2014/11/24 Javascript
jQuery实现订单提交页发送短信功能前端处理方法
2016/07/04 Javascript
在JavaScript中如何访问暂未存在的嵌套对象
2019/06/18 Javascript
使用apifm-wxapi快速开发小程序过程详解
2019/08/05 Javascript
Vue数组响应式操作及高阶函数使用代码详解
2020/08/01 Javascript
Ajax获取node服务器数据的完整步骤
2020/09/20 Javascript
JS数据类型分类及常用判断方法
2020/11/19 Javascript
vue form表单post请求结合Servlet实现文件上传功能
2021/01/22 Vue.js
[01:02:18]VGJ.S vs infamous Supermajor 败者组 BO3 第一场 6.4
2018/06/05 DOTA
一个基于flask的web应用诞生 用户注册功能开发(5)
2017/04/11 Python
Python 模拟登陆的两种实现方法
2017/08/10 Python
Python3.5 处理文本txt,删除不需要的行方法
2018/12/10 Python
tensorflow 保存模型和取出中间权重例子
2020/01/24 Python
python selenium操作cookie的实现
2020/03/18 Python
python实现一个猜拳游戏
2020/04/05 Python
python 动态渲染 mysql 配置文件的示例
2020/11/20 Python
python中zip()函数遍历多个列表方法
2021/02/18 Python
迪奥美国官网:Dior美国
2019/12/07 全球购物
简单的JAVA编程面试题
2013/03/19 面试题
PyQt 如何创建自定义QWidget
2021/03/24 Python
学校领导班子四风问题整改意见
2014/10/02 职场文书
人生感悟经典句子
2019/08/20 职场文书
pytest配置文件pytest.ini的详细使用
2021/04/17 Python