2.2. 僵尸进程和孤儿进程
示例代码: https://github.com/lotapp/BaseCode/tree/master/python/5.concurrent/Linux/base
先看看定义:
孤儿进程 https://baike.baidu.com/item/孤儿进程 : 一个父进程退出, 而它的一个或多个子进程还在运行, 那么那些子进程将成为孤儿进程. 孤儿进程将被 init 进程 (进程号为 1) 所收养, 并由 init 进程对它们完成状态收集工作.
僵尸进程 https://baike.baidu.com/item/僵尸进程 : 一个进程使用 fork 创建子进程, 如果子进程退出, 而父进程并没有调用 wait 或 waitpid 获取子进程的状态信息, 那么子进程的进程描述符仍然保存在系统中. 这种进程称之为僵死进程.
通俗讲就是:
孤儿进程: 你爸在你之前死了, 你成了孤儿, 然后你被进程 1 收养, 你死后的事宜你干爹帮你解决
僵尸进程: 你挂了, 你爸忙着干其他事情没有帮你安葬, 你变成了孤魂野鬼, 你的怨念一直长存世间
举个例子看看:
- import os
- import time
- def main():
- pid = os.fork()
- if pid == 0:
- print("子进程: Pid=%d,PPID=%d" % (os.getpid(), os.getppid()))
- time.sleep(1) # 睡 1s
- elif pid> 0:
- print("父进程: Pid=%d,PPID=%d" % (os.getpid(), os.getppid()))
- print("pid=%d,over" % os.getpid())
- if __name__ == '__main__':
- main()
输出:
- import os
- import time
- def main():
- pid = os.fork()
- if pid == 0:
- print("子进程: Pid=%d,PPID=%d" % (os.getpid(), os.getppid()))
- elif pid> 0:
- print("父进程: Pid=%d,PPID=%d" % (os.getpid(), os.getppid()))
- while True:
- print("父亲我忙着呢, 没时间管你个小屁孩")
- time.sleep(1)
- print("pid=%d,over" % os.getpid())
- if __name__ == '__main__':
- main()
输出 + 测试:
其实僵尸进程的危害真的很大, 这也就是为什么有些人为了追求效率过度调用底层, 不考虑自己实际情况最后发现还不如用自托管的效率高
僵尸进程是杀不死的, 必须杀死父类才能彻底解决它们, 下面说说怎么让父进程为子进程'收尸'
2.3. 父进程回收子进程(wait and waitpid)
讲解之前先简单分析一下上面的 Linux 指令(防止有人不太清楚)
kill -9 pid ==> 以前逆天说过, 是无条件杀死进程, 其实这种说法不准确, 应该是发信号给某个进程
-9 指的就是信号道里面的 SIGKILL(信号终止), 你写成 kill -SIGKILL pid 也一样
-9 只是系统给的一种简化写法(好像记得 1~31 信号, 各个 Linux 中都差不多, 其他的有点不一样)
- dnt@MZY-PC:~/ 桌面 / work/PC/python/Thread/Linux kill -l
- 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
- 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
- 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
- 16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
- 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
- 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
- 31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
- 38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
- 43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
- 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
- 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
- 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
- 63) SIGRTMAX-1 64) SIGRTMAX
一般搜索进程中的某个程序一般都是用这个: ps -aux | grep xxx (| 其实就是管道, 用于有血缘关系进程间通信, 等会讲)
如果安装了 pstree 就更方便了: pstree 13570 -ps (Ubuntu 自带, CentOS 要装下 yum install psmisc)
systemd(1)systemd(1160)gnome-terminal-(21604)bash(8169)python3(13570)python3(13571)
扩展: 我们平时 Ctrl+C 其实就是给 2)SIGINT 发一个信号
2.3.1.wait
代码实例: https://github.com/lotapp/BaseCode/tree/master/python/5.concurrent/Linux/wait
步入正题:
Python 的 Wait 和 C 系列的稍有不同, 这边重点说说 Python:
- help(os.wait)
- Help on built-in function wait in module posix:
- wait()
- Wait for completion of a child process.
- Returns a tuple of information about the child process:
- (pid, status)
os.wait()返回一个元组, 第一个是进程 id, 第二个是状态, 正常退出是 0, 被九号信号干死就返回 9
来个案例:
- import os
- import time
- def main():
- pid = os.fork()
- if pid == 0:
- print("子进程: Pid=%d,PPID=%d" % (os.getpid(), os.getppid()))
- elif pid> 0:
- print("父进程: Pid=%d,PPID=%d" % (os.getpid(), os.getppid()))
- wpid, status = os.wait()
- print(wpid)
- print(status)
- print("pid=%d,over" % os.getpid())
- if __name__ == '__main__':
- main()
输出:
父进程: Pid=22322,PPID=10139
子进程: Pid=22323,PPID=22322
- pid=22323,over
- 22323
- 0
- pid=22322,over
演示一下被 9 号信号干死的情况:
- import os
- import time
- def main():
- pid = os.fork()
- if pid == 0:
- print("子进程: Pid=%d,PPID=%d" % (os.getpid(), os.getppid()))
- while True:
- print("孩子老卵, 就是不听话")
- time.sleep(1)
- elif pid> 0:
- print("父进程: Pid=%d,PPID=%d" % (os.getpid(), os.getppid()))
- wpid, status = os.wait() # 调用一次只能回收一次, 想都回收, 就来个 while 循环,-1 则退出
- print(wpid)
- print(status)
- if status == 0:
- print("正常退出")
- elif status == 9:
- print("被信号 9 干死了")
- print("pid=%d,over" % os.getpid())
- if __name__ == '__main__':
- main()
输出:
扩展:(回收所有子进程, status 返回 - 1 代表没有子进程了, Python 里面没有子进程会触发异常)
- import os
- import time
- def main():
- i = 0
- while i <3:
- pid = os.fork()
- # 防止产生孙子进程(可以自己思索下)
- if pid == 0:
- break
- i += 1
- if i == 0:
- print("i=%d, 子进程: Pid=%d,PPID=%d" % (i, os.getpid(), os.getppid()))
- time.sleep(1)
- elif i == 1:
- print("i=%d, 子进程: Pid=%d,PPID=%d" % (i, os.getpid(), os.getppid()))
- time.sleep(1)
- elif i == 2:
- print("i=%d, 子进程: Pid=%d,PPID=%d" % (i, os.getpid(), os.getppid()))
- time.sleep(3)
- while True:
- print("(PID=%d)我又老卵了, 怎么滴~" % os.getpid())
- time.sleep(3)
- elif i==3: # 循环结束后, 父进程才会退出, 这时候 i=3
- print("i=%d, 父进程: Pid=%d,PPID=%d" % (i, os.getpid(), os.getppid()))
- while True:
- print("等待回收子进程")
- try:
- wpid, status = os.wait()
- print(wpid)
- print(status)
- if status == 0:
- print("正常退出")
- elif status == 9:
- print("被信号 9 干死了")
- except OSError as ex:
- print(ex)
- break
- print("pid=%d,over,ppid=%d" % (os.getpid(), os.getppid()))
- if __name__ == '__main__':
- main()
演示: 看最后一句输出, 父进程扫尾工作做完就 over 了
2.3.2.waitpid
代码实例: https://github.com/lotapp/BaseCode/tree/master/python/5.concurrent/Linux/waitpid
上面说的 wait 方法是阻塞进程的一种方式, waitpid 可以设置不阻塞进程
- help(os.waitpid)
- Help on built-in function waitpid in module posix:
- waitpid(pid, options, /)
- Wait for completion of a given child process.
- Returns a tuple of information regarding the child process:
- (pid, status)
- The options argument is ignored on Windows.
等待进程 id 为 pid 的进程结束, 返回一个 tuple, 包括进程的进程 ID 和退出信息 (和 os.wait() 一样), 参数 options 会影响该函数的行为. 在默认情况下, options 的值为 0.
如果 pid 是一个正数, waitpid()请求获取一个 pid 指定的进程的退出信息
如果 pid 为 0, 则等待并获取当前进程组中的任何子进程的值
如果 pid 为 - 1, 则等待当前进程的任何子进程
如果 pid 小于 - 1, 则获取进程组 id 为 pid 的绝对值的任何一个进程
当系统调用返回 - 1 时, 抛出一个 OSError 异常.
官方原话是这样的 https://docs.python.org/3/library/os.html#os.waitpid :(英语好的可以看看我有没有翻译错)
- If pid is greater than 0, waitpid() requests status information for that specific process.
- If pid is 0, the request is for the status of any child in the process group of the current process.
- If pid is -1, the request pertains to any child of the current process.
- If pid is less than -1, status is requested for any process in the process group -pid (the absolute value of pid).
- options:(宏)
os.WNOHANG - 如果没有子进程退出, 则不阻塞 waitpid()调用
os.WCONTINUED - 如果子进程从 stop 状态变为继续执行, 则返回进程自前一次报告以来的信息.
os.WUNTRACED - 如果子进程被停止过而且其状态信息还没有报告过, 则报告子进程的信息.
补充:
进程组: 每一个进程都属于一个 "进程组", 当一个进程被创建的时候, 它默认是其父进程所在组的成员(你们一家)
会 话: 几个进程组又构成一个会话(你们小区)
用法和 wait 差不多, 就是多了一个不阻塞线程的方法:
- import os
- import time
- def main():
- pid = os.fork()
- if pid == 0:
- print("[子进程]PID:%d,PPID:%d" % (os.getpid(), os.getppid()))
- time.sleep(2)
- elif pid> 0:
- print("[父进程]PID:%d,PPID:%d" % (os.getpid(), os.getppid()))
- while True:
- try:
- wpid, status = os.waitpid(-1, os.WNOHANG)
- if wpid> 0:
- print("回收子进程 wpid:%d, 状态 status:%d" % (wpid, status))
- except OSError:
- print("没有子进程了")
- break
- print("父进程忙着挣钱养家呢~")
- time.sleep(3)
- print("[over]PID:%d,PPID:%d" % (os.getpid(), os.getppid()))
- if __name__ == '__main__':
- main()
输出:
[父进程]PID:1371,PPID:29604
[子进程]PID:1372,PPID:1371
父进程忙着挣钱养家呢~
[over]PID:1372,PPID:1371
回收子进程 wpid:1372, 状态 status:0
父进程忙着挣钱养家呢~
没有子进程了
- [over]PID:1371,PPID:29604
- 2.3.3.wait3 and wait4
代码实例: https://github.com/lotapp/BaseCode/blob/master/python/5.concurrent/Linux/wait3.py
- help(os.wait3)
- Help on built-in function wait3 in module posix:
- wait3(options)
- Wait for completion of a child process.
- Returns a tuple of information about the child process:
- (pid, status, rusage)
- help(os.wait4)
- Help on built-in function wait4 in module posix:
- wait4(pid, options)
- Wait for completion of a specific child process.
- Returns a tuple of information about the child process:
- (pid, status, rusage)
这个是 Python 扩展的方法, 用法和 wait,waitpid 差不多, 我就不一个个的举例子了, 以 wait3 为例
- import os
- import time
- def main():
- pid = os.fork()
- if pid == 0:
- print("[子进程]PID:%d,PPID:%d" % (os.getpid(), os.getppid()))
- time.sleep(2)
- elif pid> 0:
- print("[父进程]PID:%d,PPID:%d" % (os.getpid(), os.getppid()))
- while True:
- try:
- wpid, status, rusage = os.wait3(os.WNOHANG)
- if wpid> 0:
- print("回收子进程 wpid:%d, 状态 status:%d\n 详细信息:%s" % (wpid, status,
- rusage))
- except OSError:
- print("没有子进程了")
- break
- print("父进程忙着挣钱养家呢~")
- time.sleep(3)
- print("[over]PID:%d,PPID:%d" % (os.getpid(), os.getppid()))
- if __name__ == '__main__':
- main()
输出
[父进程]PID:2638,PPID:29604
[子进程]PID:2639,PPID:2638
父进程忙着挣钱养家呢~
[over]PID:2639,PPID:2638
回收子进程 wpid:2639, 状态 status:0
详细信息: resource.struct_rusage(ru_utime=0.0052179999999999995, ru_stime=0.0052179999999999995, ru_maxrss=7032, ru_ixrss=0, ru_idrss=0, ru_isrss=0, ru_minflt=869, ru_majflt=0, ru_nswap=0, ru_inblock=0, ru_oublock=0, ru_msgsnd=0, ru_msgrcv=0, ru_nsignals=0, ru_nvcsw=2, ru_nivcsw=0)
父进程忙着挣钱养家呢~
没有子进程了
[over]PID:2638,PPID:29604
扩展: execl and execlp
代码实例: https://github.com/lotapp/BaseCode/blob/master/python/5.concurrent/Linux/execl.py
之前有说 fork 后, 相当于 copy 了一份,.text 里面放的是代码段, 如果想要调用另一个程序, 可以使用 execlxxx, 他会把. text 里面的代码替换掉
- help(os.execl)
- Help on function execl in module os:
- execl(file, *args)
- execl(file, *args)
- Execute the executable file with argument list args, replacing the
- current process.
- help(os.execlp)
- Help on function execlp in module os:
- execlp(file, *args)
- execlp(file, *args)
- Execute the executable file (which is searched for along PATH)
- with argument list args, replacing the current process.
来看个例子,
- os.execl("绝对路径","参数或者指令")
- or
- os.execlp("Path 中包含的命令","参数或者指令")
提示: 查看命令路径: eg:which ls
- import os
- def main():
- pid = os.fork()
- if pid == 0:
- # 第二个参数不能为 None,, 第一个路径为绝对路径 eg:os.execl("/bin/ls"," ")
- os.execl("/bin/ls", "ls", "-al")
- # os.execlp("ls", "ls", "-al") # 执行 Path 环境变量可以搜索到的命令
- print("exec 函数族会替换代码, 我是不会被执行的, 除非上面的出问题了")
- print("-" * 10) # 父进程执行一次, 子进程不会执行
- if __name__ == '__main__':
- main()
注意输出信息:
- os.execlp("ls", "ls", "-al")
- ----------
总用量 28
drwxrwxr-x 6 dnt dnt 4096 7 月 26 05:23 .
drwxrwxr-x 9 dnt dnt 4096 7 月 24 20:55 ..
drwxr-xr-x 2 dnt dnt 4096 7 月 19 14:47 .ipynb_checkpoints
drwxrwxr-x 6 dnt dnt 4096 7 月 26 06:27 Linux
-rw-rw-r-- 1 dnt dnt 93 7 月 26 05:49 temp.py
drwxrwxr-x 2 dnt dnt 4096 7 月 24 15:29 .vscode
drwxrwxr-x 2 dnt dnt 4096 7 月 25 12:18 进程
来源: https://www.cnblogs.com/dotnetcrazy/p/9363810.html