您的位置:首页 > 编程语言 > Python开发

python-signal异步事件

2014-11-06 09:18 302 查看
信号时一个系统的特性,它提供了一个途径可以通知程序发生了一个事件并异步处理这个时间。信号可以由系统本身产生,也可以从一个进程发送DOA另一个进程。由于信号会中断正常控制流,如果在中间接收到信号,这些操作(特别是IO操作)可能产生错误。Python在signal模块中作为符号提供了适合不同平台的多种信号。

接收信号

信号的接收要通过建立一个回调函数来接收信号,这个回调函数称为信号处理程序(signal handle),它会在信号出现时调用。信号处理程序的参数包括信号编号以及程序被信号中断那一刻的栈帧。

import signal
import os
import time

def receive_signal(signum, stack):
print 'Received:', signum

# Register signal handlers
signal.signal(signal.SIGUSR1, receive_signal)
signal.signal(signal.SIGUSR2, receive_signal)

# Print the process ID so it can be used with 'kill'
# to send this program signals
print "My PID is:", os.getpid()

while True:
print 'Warting...'
time.sleep(3)


该脚本会无限循环,每次暂停几秒时间。当一个信号来时(SIGUSR1、SIGUSR2)来时,sleep执行会被中断而去执行信号处理函数receive_signal,打印出信号编号,该函数执行完后,会回到中断处继续执行。在一个窗口中运行该程序,而在另一个终端中发送USR1,USR2信号给该进程($PID为具体的上面脚本的进程号):

$ kill -USR1 $PID
$ kill -USR2 $PID



获取注册处的程序

要查看一个信号注册了哪些信号处理程序,可以用getsignal(SIG_NUM),传入的是信号的编号,返回值是一个已注册的处理程序,或者以下某个特殊值:SIG_IGN(如果信号被忽略)、SIG_DEL(如果使用默认行为)或者None(如果C而不是从Python注册现有信号处理程序)
import signal
signals_to_names = dict((getattr(signal, n), n)
for n in dir(signal) if n.startswith("SIG") and '_' not in n)

for s, name in sorted(signals_to_names.items()):
handle = signal.getsignal(s)
if handle is signal.SIG_DFL:
handler = 'SIG_DFL'
elif handle is signal.SIG_IGN:
handle = 'SIG_IGN'
print '%-10s (%2d):' % (name, s), handler


发送信号

在python中发送信号的函数是:os.kill(pid, sig),向信号组发送信号的函数是:os.kill(pgid, sig),常用的信号有(使用“kill -l”查看):

SIGABRT由调用abort函数产生,进程非正常退出
SIGALRM用alarm函数设置的timer超时或setitimer函数设置的interval timer超时
SIGBUS某种特定的硬件异常,通常由内存访问引起
SIGCANCEL由Solaris Thread Library内部使用,通常不会使用
SIGCHLD进程Terminate或Stop的时候,SIGCHLD会发送给它的父进程。缺省情况下该Signal会被忽略
SIGCONT当被stop的进程恢复运行的时候,自动发送
SIGEMT和实现相关的硬件异常
SIGFPE数学相关的异常,如被0除,浮点溢出,等等
SIGFREEZESolaris专用,Hiberate或者Suspended时候发送
SIGHUP发送给具有Terminal的Controlling Process,当terminal被disconnect时候发送
SIGILL非法指令异常
SIGINFOBSD signal。由Status Key产生,通常是CTRL+T。发送给所有Foreground Group的进程
SIGINT由Interrupt Key产生,通常是CTRL+C或者DELETE。发送给所有ForeGround Group的进程
SIGIO异步IO事件
SIGIOT实现相关的硬件异常,一般对应SIGABRT
SIGKILL无法处理和忽略。中止某个进程
SIGLWP由Solaris Thread Libray内部使用
SIGPIPE在reader中止之后写Pipe的时候发送
SIGPOLL当某个事件发送给Pollable Device的时候发送
SIGPROFSetitimer指定的Profiling Interval Timer所产生
SIGPWR和系统相关。和UPS相关。
SIGQUIT输入Quit Key的时候(CTRL+\)发送给所有Foreground Group的进程
SIGSEGV非法内存访问
SIGSTKFLTLinux专用,数学协处理器的栈异常
SIGSTOP中止进程。无法处理和忽略。
SIGSYS非法系统调用
SIGTERM请求中止进程,kill命令缺省发送
SIGTHAWSolaris专用,从Suspend恢复时候发送
SIGTRAP实现相关的硬件异常。一般是调试异常
SIGTSTPSuspend Key,一般是Ctrl+Z。发送给所有Foreground Group的进程
SIGTTIN当Background Group的进程尝试读取Terminal的时候发送
SIGTTOU当Background Group的进程尝试写Terminal的时候发送
SIGURG当out-of-band data接收的时候可能发送
SIGUSR1用户自定义signal 1
SIGUSR2用户自定义signal 2
SIGVTALRMsetitimer函数设置的Virtual Interval Timer超时的时候
SIGWAITINGSolaris Thread Library内部实现专用
SIGWINCH当Terminal的窗口大小改变的时候,发送给Foreground Group的所有进程
SIGXCPU当CPU时间限制超时的时候
SIGXFSZ进程超过文件大小限制
SIGXRESSolaris专用,进程超过资源限制的时候发送



闹铃

闹铃(Alarm)是一种特殊的信号,程序要求操作系统在过去一段时间之后再发出这个信号通知,这对于避免一个I/O操作或者其他系统用无限阻塞很有用。所有之前设定的alarm都会失效(只能有一个alarm有效),返回值是前一个alarm设定的秒数。如果alarm设定为0秒,则取消所有的alarm:
import signal
import time

def receive_alarm(signum, stack):
print "Alarm:", time.ctime()

# Call receive_alarm in 2 seconds
signal.signal(signal.SIGALRM, receive_alarm)
print signal.alarm(4)
print signal.alarm(2)

print 'Befor:', time.ctime()
time.sleep(8)
print 'Aftter:', time.ctime()
该例子中,sleep()调用不会完成持续8秒,运行结果为:
0

4

Befor: Thu Nov 6 11:12:49 2014

Alarm: Thu Nov 6 11:12:51 2014

Aftter: Thu Nov 6 11:12:51 2014



忽略信号

要忽略一个信号,需要注册SIG_IGN作为处理程序,下面的脚本将SIGINT的默认处理程序替换为SIG_IGN,并为SIGUSR1注册一个处理程序。然后使用signal.pause()等待接受一个信号。
import signal
import os
import time

def do_exit(sig, stack):
raise SystemExit('Exiting')

signal.signal(signal.SIGINT, signal.SIG_IGN)

signal.signal(signal.SIGUSR1, do_exit)
print 'My PID:', os.getpid()
signal.pause()


运行该脚本,正常情况下,当用户按下ctrl+c向该进程发送SIGINT信号,会产生一个KeyboardInterrupt并退出该进程。但是由于这个例子忽略了SIGINT,该进程不会退出。从另一个终端用kill -USR1 5514才能退出脚本。执行结果如下:
[~/Desktop]$python test.py

My PID: 5514

^C^C^C^CExiting



信号和线程

信号和线程通常不能很好的结合,因为只有进程的主线程可以接受信号。下面的例子建立了一个信号处理程序,它在一个线程中等待信号,而从另一个线程发送信号。
import signal
import threading
import os
import time

def signal_handler(num, stack):
print "Received signal %d in %s" % \
(num, threading.currentThread().name)

signal.signal(signal.SIGUSR1, signal_handler)

def wait_for_signal():
print "Waiting for signal in", threading.currentThread().name
signal.pause()
print 'Done waiting'

def send_signal():
print "Sending signal in", threading.currentThread().name
os.kill(os.getpid(), signal.SIGUSR1)

# Start a thread that will not receive the signal
receiver = threading.Thread(target=wait_for_signal, name='receiver')
receiver.start()
time.sleep(0.1)

# Send the signal
sender = threading.Thread(target=send_signal, name='sender')
sender.start()
sender.join()

# Wait for the thread to see the signal(not going to happen)
print 'Waiting for', receiver.name
signal.alarm(2)
receiver.join()
信号处理程序都在主线程中注册,因为这是signal模块Python实现的一个要求,不论底层平台对于结合线程和信号提供怎样的支持都有这个要求。尽管接收线程调用了signal.pause(),但它不会接收信号。在结束时的signal.alarm(2)调用避免了无限的阻塞,因为接收线程永远不会退出。执行结果为:
Waiting for signal in receiver

Sending signal in sender

Received signal 10 in MainThread

Waiting for receiver

Alarm clock
尽管在任何线程中都可以设置闹铃,但总由主线程接收。
import signal
import time
import threading

def signal_handle(num, stack):
print time.ctime(),"Alarm in", threading.currentThread().name

signal.signal(signal.SIGALRM, signal_handle)

def use_alarm():
t_name = threading.currentThread().name
print time.ctime(), 'Setting alarm in', t_name
signal.alarm(1)
print time.ctime(), 'Sleeping in', t_name

# Start a thread that will not recive the signal
alarm_thread = threading.Thread(target = use_alarm, name = 'alarm_thread')
alarm_thread.start()
time.sleep(15)

# Wait for the thread to see the signal (not going to happen)
print time.ctime(), 'Waiting for', alarm_thread.name
alarm_thread.join()
print time.ctime(), 'Exiting normally'
闹铃不会终止use_alarm()中的sleep()调用,执行结果为:

[~/Desktop]$python test.py

Thu Nov 6 14:38:33 2014 Setting alarm in alarm_thread

Thu Nov 6 14:38:33 2014 Sleeping in alarm_thread

Thu Nov 6 14:38:34 2014 Alarm in MainThread

Thu Nov 6 14:38:34 2014 Waiting for alarm_thread

Thu Nov 6 14:38:34 2014 Exiting normally
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: