python-signal异步事件
2014-11-06 09:18
302 查看
信号时一个系统的特性,它提供了一个途径可以通知程序发生了一个事件并异步处理这个时间。信号可以由系统本身产生,也可以从一个进程发送DOA另一个进程。由于信号会中断正常控制流,如果在中间接收到信号,这些操作(特别是IO操作)可能产生错误。Python在signal模块中作为符号提供了适合不同平台的多种信号。
该脚本会无限循环,每次暂停几秒时间。当一个信号来时(SIGUSR1、SIGUSR2)来时,sleep执行会被中断而去执行信号处理函数receive_signal,打印出信号编号,该函数执行完后,会回到中断处继续执行。在一个窗口中运行该程序,而在另一个终端中发送USR1,USR2信号给该进程($PID为具体的上面脚本的进程号):
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
运行该脚本,正常情况下,当用户按下ctrl+c向该进程发送SIGINT信号,会产生一个KeyboardInterrupt并退出该进程。但是由于这个例子忽略了SIGINT,该进程不会退出。从另一个终端用kill -USR1 5514才能退出脚本。执行结果如下:
[~/Desktop]$python test.py
My PID: 5514
^C^C^C^CExiting
Waiting for signal in receiver
Sending signal in sender
Received signal 10 in MainThread
Waiting for receiver
Alarm clock
尽管在任何线程中都可以设置闹铃,但总由主线程接收。
[~/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
接收信号
信号的接收要通过建立一个回调函数来接收信号,这个回调函数称为信号处理程序(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除,浮点溢出,等等 |
SIGFREEZE | Solaris专用,Hiberate或者Suspended时候发送 |
SIGHUP | 发送给具有Terminal的Controlling Process,当terminal被disconnect时候发送 |
SIGILL | 非法指令异常 |
SIGINFO | BSD 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的时候发送 |
SIGPROF | Setitimer指定的Profiling Interval Timer所产生 |
SIGPWR | 和系统相关。和UPS相关。 |
SIGQUIT | 输入Quit Key的时候(CTRL+\)发送给所有Foreground Group的进程 |
SIGSEGV | 非法内存访问 |
SIGSTKFLT | Linux专用,数学协处理器的栈异常 |
SIGSTOP | 中止进程。无法处理和忽略。 |
SIGSYS | 非法系统调用 |
SIGTERM | 请求中止进程,kill命令缺省发送 |
SIGTHAW | Solaris专用,从Suspend恢复时候发送 |
SIGTRAP | 实现相关的硬件异常。一般是调试异常 |
SIGTSTP | Suspend Key,一般是Ctrl+Z。发送给所有Foreground Group的进程 |
SIGTTIN | 当Background Group的进程尝试读取Terminal的时候发送 |
SIGTTOU | 当Background Group的进程尝试写Terminal的时候发送 |
SIGURG | 当out-of-band data接收的时候可能发送 |
SIGUSR1 | 用户自定义signal 1 |
SIGUSR2 | 用户自定义signal 2 |
SIGVTALRM | setitimer函数设置的Virtual Interval Timer超时的时候 |
SIGWAITING | Solaris Thread Library内部实现专用 |
SIGWINCH | 当Terminal的窗口大小改变的时候,发送给Foreground Group的所有进程 |
SIGXCPU | 当CPU时间限制超时的时候 |
SIGXFSZ | 进程超过文件大小限制 |
SIGXRES | Solaris专用,进程超过资源限制的时候发送 |
闹铃
闹铃(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
相关文章推荐
- [python]Python Signal(信号) 异步系统事件
- Python Signal(信号) 异步系统事件
- Python Signal(信号) 异步系统事件
- Python Signal(信号) 异步系统事件
- 【ZZ】Python Signal(信号) 异步系统事件
- Python Signal(信号) 异步系统事件
- Python Signal(信号) 异步系统事件
- 详解Python实现多进程异步事件驱动引擎
- python# 进程/线程/协程 # IO:同步/异步/阻塞/非阻塞 # greenlet gevent # 事件驱动与异步IO # Select\Poll\Epoll异步IO 以及selector
- <36>python学习笔记——论事件驱动与异步IO
- 使用signal模块为异步事件设置handlers
- twisted是python实现的基于事件驱动的异步网络通信构架。
- Python运维之路——协程、事件驱动与异步IO
- signal 异步系统事件
- ActiveX异步回调JavaScript(通过事件方式)
- ATL Com 如何用线程产生异步事件
- 转载NET体系下的回调与异步调用之委托与事件机制
- 关于"Google限制Python"事件我的看法
- flex中遇到监听事件异步调用
- 一个关于委托、事件、多线程、异步的Demo