目录


摘要[TOC]

Python标准库的signal模块提供了信号处理的功能。
本文是在Python 2.7.5下测试通过的。Python3X对signal模块进行了增强,本文不做讨论。


通用规则[TOC]

signal.signal()函数用于给信号自定义处理函数。当进程收到信号时,就会执行相应的信号处理函数。
下面看一个例子:

[root@iZj6chejzrsqpclb7miryaZ ~]# cat t.py
# coding: utf8

import time
import os
import threading
import signal

IS_QUIT = False

# 信号处理函数的第一个参数是信号,第二个参数是当前的桢对象
def on_quit(signal_number, frame_object):
    print "thread: %s received signal: %d"  % (
        threading.current_thread(), signal_number)

    global IS_QUIT
    IS_QUIT = True

if __name__ == "__main__":
    signal.signal(signal.SIGUSR1, on_quit)

    print "pid is: %d" % os.getpid()
    while not IS_QUIT:
        time.sleep(0.1)

[root@iZj6chejzrsqpclb7miryaZ ~]# python t.py 
pid is: 7898

打开另外一个窗口,执行:

kill -USR1 7898

回到第一个窗口,会看到,类似下面的信息:

thread: <_MainThread(MainThread, started 140363276531520)> received signal: 10

并且程序从循环中退出了。


使用信号时,需要注意的事项[TOC]


常用函数[TOC]

1,singal.pause()[TOC]
该函数会导致进程休眠,一直到收到一个信号。同时,相应的信号处理函数也会被执行。(Windows不支持)

[root@iZj6chejzrsqpclb7miryaZ ~]# cat t.py 
import signal
import os

print "pid is: %d" % os.getpid()

signal.signal(signal.SIGINT, lambda *a, **kw: None)
signal.pause()
[root@iZj6chejzrsqpclb7miryaZ ~]# python t.py 
pid is: 7944
^C[root@iZj6chejzrsqpclb7miryaZ ~]# 

2,signal.set_wakeup_fd(fd)[TOC]
将wakeup描述符设置为fd。当收到信号时,'\0'这个字节会被写到fd。该函数通常用于唤醒poll或select调用。同时,需要信号被信号处理函数处理。
该函数会返回之前设置的wakeup描述符,如果原来没有设置过wakeup描述符,那么返回-1。如果将fd设置为-1,那么会关闭wakeup功能;否则,fd必须是非阻塞的。使用signal.set_wakeup_fd()函数的库,在再次调用poll或select之前,应该读尽fd的缓冲区。
在非主线程上调用该函数,会引发ValueError异常。

[root@iZj6chejzrsqpclb7miryaZ ~]# cat t.py 
# coding: utf8

import signal
import os
import struct
import socket

print "pid is: %d" % os.getpid()

class Waker:
    def __init__(self):
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 绑定0端口 - 操作系统会随机分配一个空闲端口
        sock.bind(("127.0.0.1", 0))
        host, port = sock.getsockname()
        sock.listen(1)

        self.writer = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.writer.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
        self.writer.connect((host, port))
        self.writer.setblocking(False)

        self.reader, _ = sock.accept()
        self.reader.setblocking(False)

        sock.close()

    def consume(self):
        try:
            while True:
                data = self.reader.recv(1024)
                if not data:
                    break
                print repr(data)
        except (IOError, socket.error) as ex:
            pass

    def get_wakeup_fd(self):
        return self.writer.fileno()

waker = Waker()

def on_sig_int(sn, fo):
    print "on_sig_int"
    waker.consume()

signal.signal(signal.SIGINT, on_sig_int)
signal.set_wakeup_fd(waker.get_wakeup_fd())

while True:
    import time
    time.sleep(0.2)
[root@iZj6chejzrsqpclb7miryaZ ~]# python t.py 
pid is: 9148
^Con_sig_int
'\x00'
^Z
[1]+  已停止               python t.py
[root@iZj6chejzrsqpclb7miryaZ ~]# kill -9 %1

[1]+  已停止               python t.py

3,定时器之:signal.alarm(time)[TOC]
如果time非0,那么该函数会在time秒之后,向进程发送SIGALRM信号,之前设置的alarm会被取消;如果time为0,则只是简单的取消之前设置的alarm。该函数的返回值表示之前设置的alarm距离投递还有多少秒。time必须是整数。

[root@iZj6chejzrsqpclb7miryaZ ~]# cat t.py 
import signal
import time

def on_sig_alarm(sn, fo):
    print "receive alarm after %s second(s)" % (time.time() - start_time)
signal.signal(signal.SIGALRM, on_sig_alarm)

signal.alarm(3)
time.sleep(1)
print signal.alarm(1)
start_time = time.time()

time.sleep(10)
[root@iZj6chejzrsqpclb7miryaZ ~]# python t.py 
2
receive alarm after 0.999886989594 second(s)

4,定时器之:signal.setitimer(which, seconds, interval)[TOC]
which用来指定定时器的类型:

seconds:表示在seconds秒后发送第一个信号
interval:表示之后每interval秒发送一个信号

seconds等于0时,会取消之前设置的which类型的定时器。

该函数返回,之前设置的which类型的定时器,距离投递还有多少秒 以及 该类型的定时器的interval

[root@iZj6chejzrsqpclb7miryaZ ~]# cat t.py 
import os
import signal

def on_sig_alrm(sn, fo):
    print "on_sig_alrm"
    print "do something here"

def handle_signal():
    signal.signal(signal.SIGALRM, on_sig_alrm)
    signal.setitimer(signal.ITIMER_REAL, 1, 1)

if __name__ == "__main__":
    print "pid: %d" % os.getpid()
    handle_signal()

    import time
    while True:
        time.sleep(0.2)
[root@iZj6chejzrsqpclb7miryaZ ~]# python t.py 
pid: 9342
on_sig_alrm
do something here
on_sig_alrm
do something here
^CTraceback (most recent call last):
  File "t.py", line 18, in 
    time.sleep(0.2)
KeyboardInterrupt

参考文档[TOC]