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

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

相关文章

一文解密Python进行监控进程的黑科技

《一文解密Python进行监控进程的黑科技》在计算机系统管理和应用性能优化中,监控进程的CPU、内存和IO使用率是非常重要的任务,下面我们就来讲讲如何Python写一个简单使用的监控进程的工具吧... 目录准备工作监控CPU使用率监控内存使用率监控IO使用率小工具代码整合在计算机系统管理和应用性能优化中,监

Linux进程CPU绑定优化与实践过程

《Linux进程CPU绑定优化与实践过程》Linux支持进程绑定至特定CPU核心,通过sched_setaffinity系统调用和taskset工具实现,优化缓存效率与上下文切换,提升多核计算性能,适... 目录1. 多核处理器及并行计算概念1.1 多核处理器架构概述1.2 并行计算的含义及重要性1.3 并

IntelliJ IDEA2025创建SpringBoot项目的实现步骤

《IntelliJIDEA2025创建SpringBoot项目的实现步骤》本文主要介绍了IntelliJIDEA2025创建SpringBoot项目的实现步骤,文中通过示例代码介绍的非常详细,对大家... 目录一、创建 Spring Boot 项目1. 新建项目2. 基础配置3. 选择依赖4. 生成项目5.

Linux线程之线程的创建、属性、回收、退出、取消方式

《Linux线程之线程的创建、属性、回收、退出、取消方式》文章总结了线程管理核心知识:线程号唯一、创建方式、属性设置(如分离状态与栈大小)、回收机制(join/detach)、退出方法(返回/pthr... 目录1. 线程号2. 线程的创建3. 线程属性4. 线程的回收5. 线程的退出6. 线程的取消7.

Linux下进程的CPU配置与线程绑定过程

《Linux下进程的CPU配置与线程绑定过程》本文介绍Linux系统中基于进程和线程的CPU配置方法,通过taskset命令和pthread库调整亲和力,将进程/线程绑定到特定CPU核心以优化资源分配... 目录1 基于进程的CPU配置1.1 对CPU亲和力的配置1.2 绑定进程到指定CPU核上运行2 基于

创建Java keystore文件的完整指南及详细步骤

《创建Javakeystore文件的完整指南及详细步骤》本文详解Java中keystore的创建与配置,涵盖私钥管理、自签名与CA证书生成、SSL/TLS应用,强调安全存储及验证机制,确保通信加密和... 目录1. 秘密键(私钥)的理解与管理私钥的定义与重要性私钥的管理策略私钥的生成与存储2. 证书的创建与

Javaee多线程之进程和线程之间的区别和联系(最新整理)

《Javaee多线程之进程和线程之间的区别和联系(最新整理)》进程是资源分配单位,线程是调度执行单位,共享资源更高效,创建线程五种方式:继承Thread、Runnable接口、匿名类、lambda,r... 目录进程和线程进程线程进程和线程的区别创建线程的五种写法继承Thread,重写run实现Runnab

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

Java进程异常故障定位及排查过程

《Java进程异常故障定位及排查过程》:本文主要介绍Java进程异常故障定位及排查过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、故障发现与初步判断1. 监控系统告警2. 日志初步分析二、核心排查工具与步骤1. 进程状态检查2. CPU 飙升问题3. 内存

python如何创建等差数列

《python如何创建等差数列》:本文主要介绍python如何创建等差数列的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录python创建等差数列例题运行代码回车输出结果总结python创建等差数列import numpy as np x=int(in