最近研究了Linux下守护进程的退出方式,简单记录以下两种方法:
一、响应退出信号
通常是SIGTERM,当使用系统命令kill杀死进程的时候,进程就会收到这个信号,SIGTERM允许该进程进行捕获并响应。(参考这里)
Python环境下,进程如果要响应SIGTERM,可以使用
signal.signal(signum, handler)
这个函数接受两个参数,具体可以参考这里。其中 handler 就是响应函数,它接受两个参数:signal number、stack frame,由于我没用到具体的参数,所以也没仔细研究那个 stack frame里面到底有什么东西。
注意几个细节:
- handler可以是成员函数,我在程序中直接使用 “signal.signal(signal.SIGTERM, self.handler)” 注册响应函数,不用担心这样会破坏handler的函数原型,python的函数名绑定机制会解决这个问题。
- 信号触发以后,handler由哪个线程来执行?我参考了这篇文章,大体是说,系统会在当前进程内寻找一个空闲线程来执行响应函数。我在程序中打印了线程名,发现在我的程序里其实是由主线程来执行响应函数的,由于我的主线程只有一个while循环不断地sleep,确实有大量空闲,所以也解释得通。
- SIGTERM触发handler被执行以后,程序流程仍然会正常进行。这意味着,如果我们不在handler中主动做退出的动作,那么程序其实并不会被杀死,而是会继续执行。通常采用的方式是:主线程中使用一个while不停地检查一个标志位,而在handler中重置这个标志位,使得主线程能够退出while循环进而终止整个程序。
二、使用atexit
这种方法相对简单,主要使用
atexit.register(func, *args, **kargs)
具体可以参考这里。
值得一提的是,与SIGTERM响应方式不同,atexit注册的函数被执行的时候,进程已经处于即将推出的状态了,因此atexit注册函数并不能阻止进程退出。同时,这也意味着如果一个进程同时注册了SIGTERM响应函数,和ateixt.register,那么SIGTMER注册函数将先被执行。
另一个需要注意的地方是,系统默认的SIGTERM处理方式,并不会触发atexit。我一开始研究atexit的时候就出现了这个问题,我一直使用默认的kill方式来杀死进程,但始终看不到atexit的执行。但是当我自行响应SIGTERM结束进程之后,atexit的注册函数也变得有效了。(此坑郁闷了我很久)