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

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中使用uv创建环境及原理举例详解

《Python中使用uv创建环境及原理举例详解》uv是Astral团队开发的高性能Python工具,整合包管理、虚拟环境、Python版本控制等功能,:本文主要介绍Python中使用uv创建环境及... 目录一、uv工具简介核心特点:二、安装uv1. 通过pip安装2. 通过脚本安装验证安装:配置镜像源(可

Windows的CMD窗口如何查看并杀死nginx进程

《Windows的CMD窗口如何查看并杀死nginx进程》:本文主要介绍Windows的CMD窗口如何查看并杀死nginx进程问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录Windows的CMD窗口查看并杀死nginx进程开启nginx查看nginx进程停止nginx服务

Java中实现线程的创建和启动的方法

《Java中实现线程的创建和启动的方法》在Java中,实现线程的创建和启动是两个不同但紧密相关的概念,理解为什么要启动线程(调用start()方法)而非直接调用run()方法,是掌握多线程编程的关键,... 目录1. 线程的生命周期2. start() vs run() 的本质区别3. 为什么必须通过 st

Java进程CPU使用率过高排查步骤详细讲解

《Java进程CPU使用率过高排查步骤详细讲解》:本文主要介绍Java进程CPU使用率过高排查的相关资料,针对Java进程CPU使用率高的问题,我们可以遵循以下步骤进行排查和优化,文中通过代码介绍... 目录前言一、初步定位问题1.1 确认进程状态1.2 确定Java进程ID1.3 快速生成线程堆栈二、分析

Macos创建python虚拟环境的详细步骤教学

《Macos创建python虚拟环境的详细步骤教学》在macOS上创建Python虚拟环境主要通过Python内置的venv模块实现,也可使用第三方工具如virtualenv,下面小编来和大家简单聊聊... 目录一、使用 python 内置 venv 模块(推荐)二、使用 virtualenv(兼容旧版 P

Linux lvm实例之如何创建一个专用于MySQL数据存储的LVM卷组

《Linuxlvm实例之如何创建一个专用于MySQL数据存储的LVM卷组》:本文主要介绍使用Linux创建一个专用于MySQL数据存储的LVM卷组的实例,具有很好的参考价值,希望对大家有所帮助,... 目录在Centos 7上创建卷China编程组并配置mysql数据目录1. 检查现有磁盘2. 创建物理卷3. 创

Python多进程、多线程、协程典型示例解析(最新推荐)

《Python多进程、多线程、协程典型示例解析(最新推荐)》:本文主要介绍Python多进程、多线程、协程典型示例解析(最新推荐),本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定... 目录一、multiprocessing(多进程)1. 模块简介2. 案例详解:并行计算平方和3. 实现逻

C#通过进程调用外部应用的实现示例

《C#通过进程调用外部应用的实现示例》本文主要介绍了C#通过进程调用外部应用的实现示例,以WINFORM应用程序为例,在C#应用程序中调用PYTHON程序,具有一定的参考价值,感兴趣的可以了解一下... 目录窗口程序类进程信息类 系统设置类 以WINFORM应用程序为例,在C#应用程序中调用python程序

正则表达式r前缀使用指南及如何避免常见错误

《正则表达式r前缀使用指南及如何避免常见错误》正则表达式是处理字符串的强大工具,但它常常伴随着转义字符的复杂性,本文将简洁地讲解r的作用、基本原理,以及如何在实际代码中避免常见错误,感兴趣的朋友一... 目录1. 字符串的双重翻译困境2. 为什么需要 r?3. 常见错误和正确用法4. Unicode 转换的

Java 如何创建和使用ExecutorService

《Java如何创建和使用ExecutorService》ExecutorService是Java中用来管理和执行多线程任务的一种高级工具,可以有效地管理线程的生命周期和任务的执行过程,特别是在需要处... 目录一、什么是ExecutorService?二、ExecutorService的核心功能三、如何创建