1 基本概述
1.1 孤儿进程和僵尸进程
父进程创建子进程后,较为理想状态是子进程结束,父进程回收子进程并释放子进程占有的资源;而实际上,父子进程是异步过程,两者谁先结束是无顺的,一般可以通过父进程调用wait()或waitpid()语句来等待子进程结束再退出。
孤儿进程:父进程结束后还有基于该父进程创建的子进程(一个或多个)尚没有结束,此时的子进程称之为孤儿进程;孤儿进程将被init进程(进程树中除了init都有父进程)接受,也就意味着init进程负责孤儿进程完成状态收集工作。一般而言,init进程的pid为1,有资料显示init有三种形式,其pid并不为1。
僵尸进程:在Linux进程状态及转换关系中有一种进程状态是僵尸状态(zombie),此时该进程称之为僵尸进程;当使用fork创建子进程后,子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程中的进程描述仍然保存在系统中,这种进程称之为僵尸进程,有时也称为僵死进程。
1.2 孤儿进程和僵尸进程危害
孤儿进程:孤儿进程不会占用系统资源,系统会将父进程回收处理孤儿进程,所以孤儿进程不占用系统资源;有时还会利用孤儿进程的这一原理进程程序逻辑设计。
僵尸进程:子进程先于父进程退出,父进程没有处理子进程的退出状态,此时子进程就会成为僵尸进程;进程已经结束,但是会占有一定的计算机资源。所以,我们应该尽量避免僵尸进程的产生。
Lunix提供了父进程获取子进程状态信息的机制;在每一个进程退出的时候,内核会释放该进程所有的资源,包括打开的文件、占用的内存等;但是仍然为其保留一定的信息(这些信息涵盖有进程号the process ID、退出状态the termination status of the process、运行时间the amount of CPU time taken by the process等),直到父进程调用wait() / waitpid()时才释放。
一个进程在调用exit命令结束自己的生命时,其实它并没有真正被销毁,而是留下一个称之为僵尸进程(Zombie)的数据结构(系统调用exit,它的作用是使进程退出,但也仅仅限于讲一个正常进程变成了一个僵尸进程,并不能将其完全销毁)。在Linux进程的状态中,僵尸进程是非常特殊的一种,它已经放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其他进程收集,除此之外,僵尸进程不再占有任何内存空间。它需要它的父进程来为它收尸,如果他的父进程没有安装SIGCHLD信号处理函数调用wait或waitpid等待子进程结束,又没有显式忽略该信号,那么它就一直保持僵尸状态,如果这时父进程结束了,那么init进程自动会接手这个子进程,为它收尸,它还是能被清除的。但是如果父进程是一个循环而不结束,那么子进程就会一直保持僵尸状态,如果如此进行下去,系统中就会遗留大量僵尸进程。
如何避免僵尸进程的产生?
1. 合理安排进程任务,尽量让父进程先结束
2. 父进程使用os.wait() os.waitpid() 来处理僵尸进程
3. 创建二级子进程,让一级子进程退出,从而形成两个完全独立的进程
4. 使用信号处理的方式
2 阻塞函数os.wait 与 os.waitpid 的基本语法
阻塞函数:当程序运行到该函数则进程处于阻塞状态不再继续运行 。一般在达到某些条件后结束阻塞
有大量IO操作或者可以给子进程操作,尽量让父进程先结束
阻塞函数不是不运行,而是等待某种事件的发生,如sleep就是阻塞等待函数
内核判断子进程是否结束,当子进程结束时,会通知应用层进行反馈
详情参考Python::OS 模块 -- 进程管理,里面详细阐述了os模块的属性
2.1 os.wait()
功能 : 阻塞等待子进程的退出,只要该父进程的任意子进程退出则终止阻塞。
参数:无
返回值:包含两个元素的元组,第一个 元素为退出的子进程的PID,第二个元素为子进程的退出码。
等待任何一个子进程结束,返回一个tuple,包括子进程的进程ID和退出状态信息:一个16位的数字,低8位是杀死该子进程的信号编号,而高8位是退出状态(如果信号编号是0),其中低8位的最高位如果被置位,则表示产生了一个core文件。
import os import sys from time import sleep pid = os.fork() if pid<0: print("create process failed") elif pid==0: print("this is chaild process:",os.getpid()) sleep(2) sys.exit(2) else: p,status = os.wait() print("this is parena process") print("p=",p,"status=",status) print(os.WEXITSTATUS(status))
运行
this is chaild process: 4088 this is parena process p= 4088 status= 512 2
注
这里status=512 而sys.exit(2)中的值是2,其实他们是256倍数关系,这是计算机本身的一种算法;可以使用os.WEXITSTATUS(status)函数来将其计算成为想要的数值,也可以使用256倍数关系进行直接运算;但是不同系统存在有不同的算法规则,所以尽量采用函数调用来计算数值
2.2 os.waitpid(pid, options)
功能: 阻塞等待子进程的退出
参数: pid -1 表示任意子进程退出都可以处理
>0 表示指定pid的子进程退出才能处理
options : 0 表示一直阻塞等待
WNOHANG: 非阻塞等待
返回值 : 同wait
os.wait() ====> os.waitpid(-1,0)
等待进程id为pid的进程结束,返回一个tuple,包括进程的进程ID和退出信息(和os.wait()一样),参数options会影响该函数的行为。在默认情况下,options的值为0。
如果pid是一个正数,waitpid()请求获取一个pid指定的进程的退出信息;
如果pid为0,则等待并获取当前进程组中的任何子进程的值;
如果pid为-1,则等待当前进程的任何子进程;
如果pid小于-1,则获取进程组id为pid的绝对值的任何一个进程;
当系统调用返回-1时,抛出一个OSError异常。
import os import sys from time import sleep pid = os.fork() if pid<0: print("create process failed") elif pid==0: print("this is chaild process:",os.getpid()) sleep(2) sys.exit(2) else: p,status = os.waitpid(-1,0)#与刚才等价 print("this is parena process") print("p=",p,"status=",status) print(os.WEXITSTATUS(status))
运行
this is chaild process: 4296 this is parena process p= 4296 status= 512 2
创建子子进程
import os pid = os.fork() if pid<0: print("create process failed") elif pid==0: p = os.fork() if p<0: pass elif p==0: print("child--->child") else: os._exit(0) else: os.wait() print("this is parena process")
运行
this is parena process child--->child
用的相关资源。