Python中使用kill -15发送SIGTERM信号时,为什么进程无法执行析构函数?

我想用 process 类记录进程的 id,写入文件 然后用使用 process 类的 close 方法关闭进程

问题是使用 kill pid 的时候,process 类的析构方法不能正常执行析构方法删除掉对应的 pid 文件

加上 signal.signal(signal.SIGTERM, self.signal_handler),即使 handler 中不写任何东西,被 kill 的进程也能调用析构方法

如果不写 signal.signal,析构方法就不会触发,求指教

python 版本为 2.7.6

Process.py

# coding=utf-8

from tool import Tool

import os import signal import re

class Process: _pid = None _pid_dir = None

prefix = 'pid.'

def __init__(self):
    t = Tool()
    self._pid_dir = t.runtime_dir()  # 创建临时目录而已,可手动写死
    signal.signal(signal.SIGTERM, self.signal_handler)

def signal_handler(self, signum, frame):
    # print('Received signal: ', signum)
    pass

def pid_file_name(self, pid):
    return self.prefix + str(pid)

def pid_file(self, pid):
    """
    获取当前进程的 pid 缓存文件
    :return:
    """
    return self._pid_dir + '/' + self.pid_file_name(pid)

def save(self, pid=None):
    """
    保存当前进程 pid 文件
    :return:
    """
    self._pid = pid if pid else os.getpid()
    os.system('touch %s' % self.pid_file(self._pid))

def close(self):
    """
    关闭全部的进程
    :return:
    """
    for pf in os.listdir(self._pid_dir):
        rer = re.match(self.prefix + '(\d+)', pf)
        if rer:
            pid = int(rer.group(1))
            try:
                os.kill(pid, 15)
            except OSError:
                # 抑制异常,不抛出进程不存在的问题,并删除不存在的进程号
                self.del_pid_file(pid)

def del_pid_file(self, pid):
    """
    删除进程缓存的文件
    :param pid:
    :return:
    """
    pid_file = self.pid_file(pid)
    os.path.exists(pid_file) and os.remove(pid_file)

def __del__(self):
    """
    如果存在当前进程的 pid 文件,删除 pid 文件
    :return:
    """
    self._pid and self.del_pid_file(self._pid)

使用 python tester.py xx 和 python tester.py 命令进行对应的调试

tester.py

import sys
import time
from core.Process import Process

import datetime

p = Process()

m = True if len(sys.argv) == 2 else False

m = False

if m: p.save() time.sleep(70) else: p.close()


Python中使用kill -15发送SIGTERM信号时,为什么进程无法执行析构函数?

23 回复

kill 的问题先不说。
m = True if len(sys.argv) == 2 else False
这是什么鬼……
直接写 m = len(sys.argv) == 2 不行么……

至于 kill term,我猜的是如果你不指定 handler,那么就用系统默认的 term handler,也就是直接杀掉进程。


这个问题涉及到Python的信号处理和进程终止机制。当使用kill -15发送SIGTERM信号时,Python进程默认会立即终止,而不会执行析构函数(__del__方法)。这是因为SIGTERM信号的处理方式是由操作系统决定的,Python默认没有为这个信号设置特殊的清理处理。

要解决这个问题,你需要为SIGTERM信号注册一个信号处理器,在收到信号时执行清理操作。下面是一个完整的示例:

import signal
import sys
import time

class MyClass:
    def __init__(self, name):
        self.name = name
        print(f"{self.name}: 初始化")
    
    def __del__(self):
        # 注意:__del__方法在信号处理中可能不会被调用
        print(f"{self.name}: 执行析构函数")
    
    def cleanup(self):
        print(f"{self.name}: 执行清理操作")

def signal_handler(signum, frame):
    print(f"收到信号 {signum},开始清理...")
    # 在这里执行清理操作
    if 'obj' in globals():
        obj.cleanup()
    sys.exit(0)

# 注册SIGTERM信号处理器
signal.signal(signal.SIGTERM, signal_handler)

# 创建对象
obj = MyClass("测试对象")

print(f"进程ID: {os.getpid()}")
print("运行中... 使用 kill -15 发送SIGTERM信号")

# 保持程序运行
try:
    while True:
        time.sleep(1)
except KeyboardInterrupt:
    print("收到Ctrl+C,退出程序")
    obj.cleanup()

关键点说明:

  1. __del__方法在信号终止时不可靠,应该使用明确的清理方法
  2. 通过signal.signal()注册SIGTERM信号处理器
  3. 在信号处理器中执行清理操作并调用sys.exit(0)正常退出
  4. 清理逻辑应该放在专门的清理方法中,而不是依赖__del__

另外,你也可以考虑使用atexit模块注册退出函数,但注意它可能在某些信号终止场景下也不会被执行。

总结:别依赖__del__,自己处理信号清理。

从 python 规范中只是说__del__会在被垃圾回收的时候调用,而且还说这种用法容易造成死锁、内存泄漏等问题,貌似从来没说过可以当做析构函数使用

kill 是信号发送
signal_handler 接受信号


你接受信号以后什么都不干 当然就没反应

本来你的对应退出代码就该写在 signal_handler 里

为什么要写 del 里?

m = xx 的问题只是一个写法的问题,每个人理解不一样而已.你理解的就是把 len 的长度与 2 的比较结果赋值给 m,我理解的是如果为 2 就赋值 true 否则赋值 false.

不指定 handler 的话,我在使用 os.kill(pid, 15)的时候,也应该通知该进程的信号是 15 啊,并没有直接杀死进程啊.
而运行的脚本这边在收到了-15 信号的时候,没有对应的进行回收,销毁变量,也就没有触发 process 这个对象的销毁,所以也么有进一步的调用到析构方法.

所有,我很迷惑.

那么你的意思是系统的默认的 term handler 无论接收到任何信号,都当成-9 来处理

这个解释看起来合理一点

啥 python 还有析构函数。。

朋友,你没有看明白问题.

__del__就是啊

__del__就是啊

python 中的析构还是少用比较好, 可以看一下 gc

操作系统默认的 term handler 接收到 term 的时候,除了当成 kill 以外好像也没有别的选择啊。
系统也不可能进入到你程序的内部来退出吧。

我记得 term 是由 glibc 的默认 handler 处理
kill 是内核直接删进程
区别在于:如果进程 IO block,term 是没有用的,要 kill

嗯应该没错。
IO block 的话,你说的是 D 状态?那个连 kill 都没用的。

kill 是内核直接抽内存栈了啊
term 实际上是进程自己处理的,所以自己指定一个 handler 的话是可以不死的
但是 kill 表面上是个信号,但是根本不会被送到进程内,实际上是内核直接处理的

是啊我同意你的观点。kill 是没有 handler 的,term 的默认 handler 类似 kill self。
我说的是 IO block 的情况,是内核自己炸了,所以 kill 也没用。

呃,举个例子,读写 NFS,结果网断了
我是说这种情况

是啊,NFS 断线是我遇到的最多的 kill 不掉的场景了。
没记错的话需要用 hup 去捅挂载进程,然后才能 kill 掉程序。
你直接 kill 的话,D 状态的程序是 kill 不掉的,否则内核状态会出问题。

在这种情况下 kill 明确是没有用的,因为 stuck 在内核里

析构函数本来就不一定运行啊。

你 C++ 写一个析构函数,然后发送一个导致杀死进程的信号,outstanding 对象的析构函数也不会运行。

另外,del 应该类比于 C# 的 finalizer,而不是 C++ destructor,Python 文档也是这么说的。

终结化器不一定非要运行,包括程序正常退出的情况。终结化器不一定按照你想像的顺序运行,比如,当终结化器运行的时候,或许你的一个字段引用的对象已经被终结化了( C# 也有该现象)。



看来是我记错了,抱歉

受教了,我去多读读 python 的关于这点的文档

回到顶部