Python信号处理

终止信号

通常我们认为,在try语句中,finally一定会执行。

# coding: utf-8
import time
import os
import logging

try:
    print 'start try, sleep 30s...'
    print 'pid: %s' % os.getpid()
    time.sleep(30)
    print 'end try'
except Exception, e:
    print 'catch exception'
    logging.exception(e)
except KeyboardInterrupt, e:
    logging.exception(e)
finally:
    print 'oh, finally'

上面这段程序,用Ctrl-c来终止程序时,可以捕获到一个KeyboardInterrupt,finally也会执行。

start try, sleep 30s...
pid: 9900
oh, finally
ERROR:root:
Traceback (most recent call last):
  File "/Users/bowen/python/learn-python/exceptions/finally_test.py", line 9, in <module>
    time.sleep(30)
KeyboardInterrupt

但是如果在命令行,采用 kill -9 PID方式终止程序,程序会强制退出,finally后面的代码段也不会执行。

那么两种情况不同是什么原因呢?
本质区别在于python进程对于信号的处理。信号(signal)是进程之间通讯的方式,是一种软件中断。一个进程一旦接收到信号就会打断原来的程序执行流程来处理信号。
几个常用信号:

SIGINT    终止进程  中断进程  (control+c)
SIGTERM   终止进程  软件终止信号
SIGKILL   终止进程  杀死进程
SIGALRM   闹钟信号
SIGCHLD   子进程终止向父进程发送的信号

control+c会发送SIGINT信号,并产生KeyboardInterrupt异常。kill -9会发送SIGKILL信号。SIGTERM和SIGKILL都是进程结束信号。

SIGKILL信号是无法在程序内部捕获的,一旦发送SIGKILL信号给进程,Linux就将进程停止在那里。Python自己并不检查SIGKILL,而是直接把底层标准C的运行时错误返回。在Python代码中,SIGKILL无法捕捉,而且无法忽略。一般关于sigkill的日志在/var/log/messages里,如果非deamon程序在终端也是有日志体现的。

SIGTERM比较友好,进程能捕捉这个信号,根据需要来关闭程序。在关闭程序之前,可以结束打开的记录文件和完成正在做的任务。在某些情况下,假如进程正在进行作业而且不能中断,那么进程可以忽略这个SIGTERM信号。

发送信号一般有两种原因:
1. (被动式) 内核检测到一个系统事件。例如子进程退出会像父进程发送SIGCHLD信号.键盘按下control+c会发送SIGINT信号。
2. (主动式) 通过系统调用kill来向指定进程发送信号。

python提供的信号:

>>> import signal
>>> dir(signal)

['NSIG', 'SIGABRT', 'SIGALRM', 'SIGBUS', 'SIGCHLD', 'SIGCLD', 'SIGCONT', 'SIGFPE', 'SIGHUP', 'SIGILL', 'SIGINT', 'SIGIO', 'SIGIOT', 'SIGKILL', 'SIGPIPE', 'SIGPOLL', 'SIGPROF', 'SIGPWR', 'SIGQUIT', 'SIGRTMAX', 'SIGRTMIN', 'SIGSEGV', 'SIGSTOP', 'SIGSYS', 'SIGTERM', 'SIGTRAP', 'SIGTSTP', 'SIGTTIN', 'SIGTTOU', 'SIGURG', 'SIGUSR1', 'SIGUSR2', 'SIGVTALRM', 'SIGWINCH', 'SIGXCPU', 'SIGXFSZ', 'SIG_DFL', 'SIG_IGN', '__doc__', '__name__', 'alarm', 'default_int_handler', 'getsignal', 'pause', 'signal']

操作系统规定了进程收到信号以后的默认行为。但是,我们可以通过绑定信号处理函数来修改进程收到信号以后的行为,有两个信号是不可更改的:SIGTOP和SIGKILL。

处理信号

子进程向父进程发送一个signal.SIGTERM信号的例子。

# coding: utf-8
import os
import signal
import time


def catch_signal(a, b):
    print 'catch signal, %s' % os.getpid()


signal.signal(signal.SIGTERM, catch_signal)

try:
    pid = os.fork()
    if pid == 0:
        print 'child process, %s' % os.getpid()
        time.sleep(1)
        # Kill a process with a signal.
        os.kill(os.getppid(), signal.SIGTERM)
    else:
        print 'parent process, %s' % os.getpid()
        print 'wait child ...'
        os.wait()
except Exception, e:
    print e
    pass

print 'end %s' % os.getpid()

运行结果:

parent process, 14901
wait child ...
child process, 14902
end 14902
catch signal, 14901
[Errno 4] Interrupted system call
end 14901

注意

如果一个进程收到一个SIGUSR1信号,然后执行信号绑定函数,第二个SIGUSR2信号又来了,第一个信号没有被处理完毕的话,第二个信号就会丢弃。所以,尽量不要在多线程中使用信号。

你可能感兴趣的:(Python基础)