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

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

相关文章

Linux kill正在执行的后台任务 kill进程组使用详解

《Linuxkill正在执行的后台任务kill进程组使用详解》文章介绍了两个脚本的功能和区别,以及执行这些脚本时遇到的进程管理问题,通过查看进程树、使用`kill`命令和`lsof`命令,分析了子... 目录零. 用到的命令一. 待执行的脚本二. 执行含子进程的脚本,并kill2.1 进程查看2.2 遇到的

Java 虚拟线程的创建与使用深度解析

《Java虚拟线程的创建与使用深度解析》虚拟线程是Java19中以预览特性形式引入,Java21起正式发布的轻量级线程,本文给大家介绍Java虚拟线程的创建与使用,感兴趣的朋友一起看看吧... 目录一、虚拟线程简介1.1 什么是虚拟线程?1.2 为什么需要虚拟线程?二、虚拟线程与平台线程对比代码对比示例:三

k8s按需创建PV和使用PVC详解

《k8s按需创建PV和使用PVC详解》Kubernetes中,PV和PVC用于管理持久存储,StorageClass实现动态PV分配,PVC声明存储需求并绑定PV,通过kubectl验证状态,注意回收... 目录1.按需创建 PV(使用 StorageClass)创建 StorageClass2.创建 PV

Linux创建服务使用systemctl管理详解

《Linux创建服务使用systemctl管理详解》文章指导在Linux中创建systemd服务,设置文件权限为所有者读写、其他只读,重新加载配置,启动服务并检查状态,确保服务正常运行,关键步骤包括权... 目录创建服务 /usr/lib/systemd/system/设置服务文件权限:所有者读写js,其他

idea+spring boot创建项目的搭建全过程

《idea+springboot创建项目的搭建全过程》SpringBoot是Spring社区发布的一个开源项目,旨在帮助开发者快速并且更简单的构建项目,:本文主要介绍idea+springb... 目录一.idea四种搭建方式1.Javaidea命名规范2JavaWebTomcat的安装一.明确tomcat

Python异常处理之避免try-except滥用的3个核心原则

《Python异常处理之避免try-except滥用的3个核心原则》在Python开发中,异常处理是保证程序健壮性的关键机制,本文结合真实案例与Python核心机制,提炼出避免异常滥用的三大原则,有需... 目录一、精准打击:只捕获可预见的异常类型1.1 通用异常捕获的陷阱1.2 精准捕获的实践方案1.3

C#使用SendMessage实现进程间通信的示例代码

《C#使用SendMessage实现进程间通信的示例代码》在软件开发中,进程间通信(IPC)是关键技术之一,C#通过调用WindowsAPI的SendMessage函数实现这一功能,本文将通过实例介绍... 目录第一章:SendMessage的底层原理揭秘第二章:构建跨进程通信桥梁2.1 定义通信协议2.2

Git打标签从本地创建到远端推送的详细流程

《Git打标签从本地创建到远端推送的详细流程》在软件开发中,Git标签(Tag)是为发布版本、标记里程碑量身定制的“快照锚点”,它能永久记录项目历史中的关键节点,然而,仅创建本地标签往往不够,如何将其... 目录一、标签的两种“形态”二、本地创建与查看1. 打附注标http://www.chinasem.cn

Spring创建Bean的八种主要方式详解

《Spring创建Bean的八种主要方式详解》Spring(尤其是SpringBoot)提供了多种方式来让容器创建和管理Bean,@Component、@Configuration+@Bean、@En... 目录引言一、Spring 创建 Bean 的 8 种主要方式1. @Component 及其衍生注解

Linux系统管理与进程任务管理方式

《Linux系统管理与进程任务管理方式》本文系统讲解Linux管理核心技能,涵盖引导流程、服务控制(Systemd与GRUB2)、进程管理(前台/后台运行、工具使用)、计划任务(at/cron)及常用... 目录引言一、linux系统引导过程与服务控制1.1 系统引导的五个关键阶段1.2 GRUB2的进化优