如何将主进程创建的子进程终止,避免形成孤儿进程

2024-03-26 15:58

本文主要是介绍如何将主进程创建的子进程终止,避免形成孤儿进程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一,linux中,kill -9和kill -15的区别

1,kill -l(查看Linux/Unix的信号变量)

用来查看kill命令中可以带哪些 “信号编号”

在这里插入图片描述

2,常使用的kill -9、kill -15的区别
1)kill -15

系统会发送一个SIGTERM的信号给对应的程序。当程序接收到该signal后,将会发生以下的事情:

  • 程序立刻停止
  • 当程序释放相应资源后再停止
  • 程序可能仍然继续运行

大部分程序接收到SIGTERM信号后,会先释放自己的资源,然后在停止。但是也有程序可以在接受到信号量后,做一些其他的事情,并且这些事情是可以配置的。如果程序正在等待IO,可能就不会立马做出相应。也就是说,SIGTERM多半是会被阻塞的、忽略。

2)kill -9

你不是可以不响应 SIGTERM吗?那好,我给你下一道必杀令,我看你还不乖乖的。多半admin会用这个命令不过,也不是所有的程序都会乖乖听话,总有那些状态下的程序无法立刻相应。

二,在多进程中杀掉父进程可能出现的问题

        在Python中,由于全局解释器锁GIL的存在,使得Python中的多线程并不能大大提高程序的运行效率,那么在处理CPU密集型计算时,多用多进程模型来处理
        而Python标准库中提供了multiprocessing库来支持多进程模型的编程。multiprocessing中提供了的Process类用于开发人员编写创建子进程,接口类似于标准库提供的threading.Thread类,还提供了进程池Pool类,减少进程创建和销毁带来开销,用以提高复用
        在多线程模型中,默认情况下(sub-Thread.daemon=False)主线程会等待子线程退出后再退出,而如果sub-Thread.setDaemon(True)时,主线程不会等待子线程,直接退出,而此时子线程会随着主线程的对出而退出,避免这种情况,主线程中需要对子线程进行join,等待子线程执行完毕后再退出。对应的,在多进程模型中,Process类也有daemon属性,而它表示的含义与Thread.daemon类似,当设置sub-Process.daemon=True时,主进程中需要对子进程进行等待,否则子进程会随着主进程的退出而退出

简单多进程实例如下:

import threading
import time
import multiprocessingdef fun(args):for i in range(100):print argstime.sleep(1)if __name__ == '__main__':threads = []for i in range(4):# t = threading.Thread(target=fun, args=(str(i),))# t.setDaemon(True)t = multiprocessing.Process(target=fun, args=(str(i),))t.daemon = Truet.start()threads.append(t)for i in threads:i.join()

        运行上面的代码,主进程会等待子进程执行结束后退出,整个程序结束。 而当有人为的干扰时,例如在进程启动之后,通过kill -9将进程杀死时,情况就不同了,我们知道多线程模型再复杂,也只是在同一个进程中,杀死主进程,所有的线程都会随着主进程的退出而退出,而多进程模型中,每个进程都是独立的,在杀死主进程之后,其他子进程并不会受到影响,还会继续运行。如果在父进程被杀死后,没有有效回收子进程,这样的话就比较麻烦,需要人工的杀死。
  对于这种情况,首先想到的是用信号signal来处理,这样一来,在杀死主进程时就不能再用kill -9命令了,因为kill -9命令表示向进程发送SIGKILL命令,而 在系统中,SIGKILL(kill -9)和SIGSTOP两种信号,进程是无法捕获的,收到后会立即退出。 在linux下执行kill -l,可以看到全部的信号量,这里使用SIGTERM信号(kill -15),SIGTERM表示终止信号,是kill命令传送的系统默认信号,它与SIGKIIL的区别是,SIGTERM更为友好,进程能捕捉SIGTERM信号,进而根据需要来做一些清理工作。

三,如何将主进程创建的子进程终止,避免形成孤儿进程,两种做法

1,通过进程组id将整个进程组中的进程杀死。

当我们在主进程中创建子进程时,主进程与其创建的子进程隶属于同一个分组里,这个分组的概念在linux中成为进程组,它是一个或多个进程的组成的集合,同一个进程组中的进程,它们的进程组ID是一致的。利用python标准库中os.getpgid方法,通过进程的ID来获取进程对应的组ID,接着调用os.killpg方法,向进程的组ID发送信号。

1 def fun(x):2     print 'current pid is %s, group id is %s' % (os.getpid(), os.getpgrp())3     while True:4         print 'args is %s ' % x5         time.sleep(1)6 7 8 def term(sig_num, addtion):9     print 'current pid is %s, group id is %s' % (os.getpid(), os.getpgrp())
10     os.killpg(os.getpgid(os.getpid()), signal.SIGKILL)
11 
12 
13 if __name__ == '__main__':
14     signal.signal(signal.SIGTERM, term)
15     print 'current pid is %s' % os.getpid()
16     for i in range(3):
17         t = Process(target=fun, args=(str(i),))
18         t.daemon = True
19         t.start()
20         processes.append(t)
21     
22     try:
23         for p in processes:
24             p.join() 
25     except Exception as e:
26         print str(e)

注意在代码中,为了防止之前出现的无限循环,在term函数中,我们通过os.killpg,直接向进程组发送SIGKILL信号。运行代码,通过输出我们可以看出,进程组中,主进程和子进程的进程组id相同,都是主进程的pid。通过kill -15向主进程或者子进程发送SIGTERM信号时,都会将进程组主进程和子进程全部杀死。

2,使用信号处理机制,在主进程收到终止信号SIGTERM时,保存的子进程信息terminate,之后主进程退出
(1)示例:

能够将processes通过函数调用,传递给回调函数,避免使用全局变量。python标准库functools向我们提供了partial偏函数,它的用途是让一些参数在函数被调用之前提前获知其值,位置参数和关键字参数均可应用,我们来看个例子:

from functools import partial
def add(a, b):return a + badd_with_hundred = partial(add, 100)
result = add_with_hundred(10)
print result
110

代码示例中,partial(add, 100)返回一个partial对象,参数add表示要封装的方法,参数100表示位置参数,它表示的位置是add方法中第一个参数,相当于对add方法的第一个参数添加了默认值100,对于返回的add_with_hundred对象,它的第一个参数默认已经是100,那么在使用时只需要传入一个参数即可。再来看一个关键字参数的例子:

from functools import partial
basetwo = partial(int, base=2)
result = basetwo('101')
print result
5
(2):使用partial偏函数,并通过返回partial对象实现传递参数
def fun(x):print 'current pid is %s, group id is %s' % (os.getpid(), os.getpgrp())while True:print 'args is %s ' % xtime.sleep(1)def term(t_processes, sig_num, frame):print 'terminate process %d' % os.getpid()try:print 'the processes is %s' % processesfor p in processes:print 'process %d terminate' % p.pidp.terminate()except Exception as e:print str(e)if __name__ == '__main__':print 'current main-process pid is %s' % os.getpid()processes = []for i in range(3):t = Process(target=fun, args=(str(i),))t.daemon = Truet.start()processes.append(t)# handler使用partital处理,用local processes对term方法的第一个参数进行绑定handler = functools.partial(term, processes)signal.signal(signal.SIGTERM, handler)try:for p in processes:p.join()except Exception as e:print str(e)

四,使用进程池multiprocessing.Pool时,如何保证主进程意外退出,进程池中的worker进程同时退出,不产生孤儿进程

此处介绍两种方法

1,主进程中使用进程池,kill -15 杀掉进程群组。

在新建worker进程时,默认启动方式为daemon,这种情况下worker进程作为主进程的子进程,会随着主进程的退出而退出。查看源码如下:

def _repopulate_pool(self):"""Bring the number of pool processes up to the specified number,for use after reaping workers which have exited."""for i in range(self._processes - len(self._pool)):w = self.Process(target=worker,args=(self._inqueue, self._outqueue,self._initializer,self._initargs, self._maxtasksperchild,self._wrap_exception))self._pool.append(w)w.name = w.name.replace('Process', 'PoolWorker')w.daemon = Truew.start()util.debug('added worker')

代码实现如下:

import time
import os
import signal
from multiprocessing import Pooldef fun(x):print ('current sub-process pid is %s' % os.getpid())while True:print(x)time.sleep(2)def term(sig_num, addtion):print ('current pid is %s, group id is %s' % (os.getpid(), os.getpgrp()))os.killpg(os.getpgid(os.getpid()), signal.SIGKILL)if __name__ == '__main__':print ('current pid is %s' % os.getpid())mul_pool = Pool()signal.signal(signal.SIGTERM, term)for i in range(3):mul_pool.apply_async(func=fun, args=(i,))mul_pool.close()mul_pool.join()

这么做是有些武断,如果一些worker进程在运行一些重要的业务逻辑,强制结束可能会使得数据的丢失,或者一些其他难以恢复的后果,那么有没有更合理的处理方式,使worker进程在处理完本轮数据后,再退出呢?答案同样是肯定的

2,主进程中使用进程池,用Event来控制worker进程的退出

python标准库中提供了一些进程间同步的工具,这里我们使用Event对象来做同步。首先我们需要通过multiprocessing.Manager类来获取一个Event对象,用Event来控制worker进程的退出

import time
import os
import signal
import functools
from multiprocessing import Pool
from multiprocessing import Managerdef fun(x,event):while True:print("%s进程%s开始运行..."%(str(x),str(os.getpid())))time.sleep(1)print("%s进程%s运行中..." % (str(x), str(os.getpid())))time.sleep(1)print("%s进程%s运行OK!..." % (str(x), str(os.getpid())))time.sleep(1)if event.is_set():breakdef term(pool,event,manager,sig_num, addtion):print ('current pid is %s, group id is %s' % (os.getpid(), os.getpgrp()))if not event.is_set():event.set()pool.close()pool.join()manager.shutdown()print('exit ...')os._exit(0)# os.killpg(os.getpgid(os.getpid()), signal.SIGKILL)if __name__ == '__main__':print ('current pid is %s' % os.getpid())mul_pool = Pool()manager = Manager()event = manager.Event()handler = functools.partial(term,mul_pool,event,manager)signal.signal(signal.SIGTERM,handler)for i in range(3):mul_pool.apply_async(func=fun, args=(i,event))mul_pool.close()mul_pool.join()

参考自:
主进程被杀死时,如何保证子进程同时退出,而不变为孤儿进程
《主进程被杀死时,如何保证子进程同时退出,而不变为孤儿进程(一)》
《主进程被杀死时,如何保证子进程同时退出,而不变为孤儿进程(二)》
《主进程被杀死时,如何保证子进程同时退出,而不变为孤儿进程(三)》

这篇关于如何将主进程创建的子进程终止,避免形成孤儿进程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/849037

相关文章

JAVA中整型数组、字符串数组、整型数和字符串 的创建与转换的方法

《JAVA中整型数组、字符串数组、整型数和字符串的创建与转换的方法》本文介绍了Java中字符串、字符数组和整型数组的创建方法,以及它们之间的转换方法,还详细讲解了字符串中的一些常用方法,如index... 目录一、字符串、字符数组和整型数组的创建1、字符串的创建方法1.1 通过引用字符数组来创建字符串1.2

手把手教你idea中创建一个javaweb(webapp)项目详细图文教程

《手把手教你idea中创建一个javaweb(webapp)项目详细图文教程》:本文主要介绍如何使用IntelliJIDEA创建一个Maven项目,并配置Tomcat服务器进行运行,过程包括创建... 1.启动idea2.创建项目模板点击项目-新建项目-选择maven,显示如下页面输入项目名称,选择

C#如何优雅地取消进程的执行之Cancellation详解

《C#如何优雅地取消进程的执行之Cancellation详解》本文介绍了.NET框架中的取消协作模型,包括CancellationToken的使用、取消请求的发送和接收、以及如何处理取消事件... 目录概述与取消线程相关的类型代码举例操作取消vs对象取消监听并响应取消请求轮询监听通过回调注册进行监听使用Wa

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

在cscode中通过maven创建java项目

在cscode中创建java项目 可以通过博客完成maven的导入 建立maven项目 使用快捷键 Ctrl + Shift + P 建立一个 Maven 项目 1 Ctrl + Shift + P 打开输入框2 输入 "> java create"3 选择 maven4 选择 No Archetype5 输入 域名6 输入项目名称7 建立一个文件目录存放项目,文件名一般为项目名8 确定

Java 创建图形用户界面(GUI)入门指南(Swing库 JFrame 类)概述

概述 基本概念 Java Swing 的架构 Java Swing 是一个为 Java 设计的 GUI 工具包,是 JAVA 基础类的一部分,基于 Java AWT 构建,提供了一系列轻量级、可定制的图形用户界面(GUI)组件。 与 AWT 相比,Swing 提供了许多比 AWT 更好的屏幕显示元素,更加灵活和可定制,具有更好的跨平台性能。 组件和容器 Java Swing 提供了许多

[Linux]:进程(下)

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ 🎈🎈养成好习惯,先赞后看哦~🎈🎈 所属专栏:Linux学习 贝蒂的主页:Betty’s blog 1. 进程终止 1.1 进程退出的场景 进程退出只有以下三种情况: 代码运行完毕,结果正确。代码运行完毕,结果不正确。代码异常终止(进程崩溃)。 1.2 进程退出码 在编程中,我们通常认为main函数是代码的入口,但实际上它只是用户级

顺序表之创建,判满,插入,输出

文章目录 🍊自我介绍🍊创建一个空的顺序表,为结构体在堆区分配空间🍊插入数据🍊输出数据🍊判断顺序表是否满了,满了返回值1,否则返回0🍊main函数 你的点赞评论就是对博主最大的鼓励 当然喜欢的小伙伴可以:点赞+关注+评论+收藏(一键四连)哦~ 🍊自我介绍   Hello,大家好,我是小珑也要变强(也是小珑),我是易编程·终身成长社群的一名“创始团队·嘉宾”

Maven创建项目中的groupId, artifactId, 和 version的意思

文章目录 groupIdartifactIdversionname groupId 定义:groupId 是 Maven 项目坐标的第一个部分,它通常表示项目的组织或公司的域名反转写法。例如,如果你为公司 example.com 开发软件,groupId 可能是 com.example。作用:groupId 被用来组织和分组相关的 Maven artifacts,这样可以避免

批处理以当前时间为文件名创建文件

批处理以当前时间为文件名创建文件 批处理创建空文件 有时候,需要创建以当前时间命名的文件,手动输入当然可以,但是有更省心的方法吗? 假设我是 windows 操作系统,打开命令行。 输入以下命令试试: echo %date:~0,4%_%date:~5,2%_%date:~8,2%_%time:~0,2%_%time:~3,2%_%time:~6,2% 输出类似: 2019_06