先简单说一下Signal是啥.(如果想直接使用可以不看)
Signal翻译过来中文就是信号- -
当然, 本身他就是Linux系统编程中非常重要的概念, 信号机制是进程之间传递消息的一种机制,
其全称为软中断信号
作用是通知进程发生了异步事件。进程之间可以调用系统来传递信号, 本身内核也可以发送信号给进程, 告诉该进程发生了某个事件.
注意,信号只是用来通知某进程发生了什么事件,并不给该进程传递任何数据。
接收信号的进程对不同的信号有三种处理方法
- 指定处理函数
- 忽略
- 根据系统默认值处理, 大部分信号的默认处理是终止进程
然后就是一大段类型了..
Linux系统有两大类信号
- POSIX标准的规则信号(regular signal 1-31编号)
- 实时信号(real-time signal 32-63)
规则信号
信号编号 | 名称 | 默认动作 | 说明 |
---|---|---|---|
1 | SIGHUP | 终止 | 终止控制终端或进程 |
2 | SIGINT | 终止 | 由键盘引起的终端(Ctrl-c) |
3 | SIGQUIT | dump | 控制终端发送给进程的信号, 键盘产生的退出(Ctrl-\), |
4 | GIGILL | dusmp | 非法指令引起 |
5 | SIGTRAP | dump | debug中断 |
6 | SIGABRT/SIGIOT | dump | 异常中止 |
7 | SIGBUS/SIGEMT | dump | 总线异常/EMT指令 |
8 | SIGFPE | dump | 浮点运算溢出 |
9 | SIGKILL | 终止 | 强制杀死进程(大招, 进程不可捕获) |
10 | SIGUSR1 | 终止 | 用户信号, 进程可自定义用途 |
11 | SIGSEGV | dump | 非法内存地址引起 |
12 | SIGUSR2 | 终止 | 用户信号, 进程可自定义用途 |
13 | SIGPIPE | 终止 | 向某个没有读取的管道中写入数据 |
14 | SIGALRM | 终止 | 时钟中断(闹钟) |
15 | SIGTERM | 终止 | 进程终止(进程可捕获) |
16 | SIGSTKFLT | 终止 | 协处理器栈错误 |
17 | SIGCHLD | 忽略 | 子进程退出或中断 |
18 | SIGCONT | 继续 | 如进程停止状态则开始运行 |
19 | SIGSTOP | ֹͣ | 停止进程运行 |
20 | SIGSTP | ֹͣ | 键盘产生的停止 |
21 | SIGTTIN | ֹͣ | 后台进程请求输入 |
22 | SIGTTOU | ֹͣ | 后台进程请求输出 |
23 | SIGURG | 忽略 | socket发送紧急情况 |
24 | SIGXCPU | dump | CPU时间限制被打破 |
25 | SIGXFSZ | dump | 文件大小限制被打破 |
26 | SIGVTALRM | 终止 | 虚拟定时时钟 |
27 | SIGPROF | 终止 | profile timer clock |
28 | SIGWINCH | 忽略 | 窗口尺寸调整 |
29 | SIGIO/SIGPOLL | 终止 | I/O可用 |
30 | SIGPWR | 终止 | 电源异常 |
31 | SIGSYS/SYSUNUSED | dump | 系统调用异常 |
注意: 由于不同系统中同一个数值对应的信号类型不一样, 所以最好使用信号名称.
信号的数值越小, 优先级越高.
OK, 现在来说说Python中的处理
先列几个常用的信号:
编号 | 信号名称 | 说明 |
---|---|---|
2 | SIGINT | 当按下键盘(Ctrl-c)组合键时进程就会收到这个信号 |
15 | SIGTERM | 当用户输入 kill sigterm pid. 对应的进程就会收到这个信号. 这个信号进程是可以捕获并指定函数处理, 例如做一下程序清理等工作. 甚至忽视这个信号 |
9 | SIGKILL | 强制杀死进程, 这个信号进程无法忽视, 直接在系统层面把进程杀掉. 所以在Python中他的不能监听的 |
14 | SIGALRM | 闹钟信号 |
去码
先来一个例子
#!/usr/bin/env python # -*- coding: utf-8 -*- """ 监听了SIGINT信号, 当程序在运行的时候同时按下键盘 Ctrl+c 就会输出 收到信号 2 <frame object at 0x00000000021DD048> handler方法的两个参数分别是 信号编号, 程序帧 """ import sys reload(sys) sys.setdefaultencoding("utf-8") import time import os import signal receive_times = 0 def handler(signalnum, handler): global receive_times print u"收到信号", signalnum, frame, receive_times receive_times += 1 if receive_times > 3: exit(0) # 自己走 def main(): signal.signal(signal.SIGINT, handler) # Ctrl-c # time.sleep(10) # SIGINT 信号同样能唤醒 time.sleep, 所以这里程序就会结束 while True: # 改成 while 效果会好点 pass if __name__ == '__main__': main()
再看看SIGTERM
的效果
#!/usr/bin/env python # -*- coding: utf-8 -*- """ 当我们运行该程序时因为 while True 所以会持续的运行. 这里监听的是 SIGTERM 信号, 所以当我们在终端输入 kill pid (linux kill 默认是发送SIGTERM)时, 程序就会输出: 收到信号 15 <frame object at 0x7ff695738050> 0 当超过3次时就强制把自己杀死. 所以 SIGTERM 很适合用来做一些清理的工作 """ import sys reload(sys) sys.setdefaultencoding("utf-8") import time import os import signal receive_times = 0 def handler(signalnum, frame): global receive_times print u"收到信号", signalnum, frame, receive_times receive_times += 1 if receive_times > 3: exit(0) # 自己走 def main(): print "pid:", os.getpid() signal.signal(signal.SIGTERM, handler) while True: pass if __name__ == '__main__': main()
刚才我们说过SIGKILL不能被监听.
signal.signal(signal.SIGKILL, handler) # 这里系统会直接跑错 AttributeError: 'module' object has no attribute 'SIGKILL'
最后来一个实际运用的例子
在python2.x的版本, 线程有个bug, 在join的时候不能接收信号
详解见:https://bugs.python.org/issue1167930
所以如果我们运行以下代码
#!/usr/bin/env python # -*- coding: utf-8 -*- """ 这里虽然我们监听了 SIGINT 信号, 但是当我们按下Ctrl-c时程序并没有任何输出. 还是要等线程运行完成程序才退出. """ import sys reload(sys) sys.setdefaultencoding("utf-8") import time import os import signal import threading receive_times = 0 def handler(signalnum, frame): global receive_times print u"收到信号", signalnum, frame, receive_times receive_times += 1 if receive_times > 3: # os.kill(os.getpid(), signal.SIGTERM) # 我疯起来连自己都杀 exit(0) def run(): print "thread %s run:"%(threading.currentThread().getName()) time.sleep(10) print "thread %s done"%(threading.currentThread().getName()) def main(): print "pid:", os.getpid() signal.signal(signal.SIGINT, handler) thread_list = [] for i in range(5): thread = threading.Thread(target = run) thread_list.append(thread) for thread in thread_list: thread.start() for thread in thread_list: thread.join() print "all thread done" if __name__ == '__main__': main()
然后我们来改一下
#!/usr/bin/env python # -*- coding: utf-8 -*- """ 在这里我们放弃了线程的join() 方法, 然后用 while True 的方式来代替, 然后在主进程判断线程的存活状态. 这样既能持续的运行线程又能根据需求来随时中断 """ import sys reload(sys) sys.setdefaultencoding("utf-8") import time import os import signal import threading is_run_thread = True def handler(signalnum, frame): print u"收到信号", signalnum, frame global is_run_thread is_run_thread = False # 停止运行线程 def run(): print "thread %s run:"%(threading.currentThread().getName()) while is_run_thread: # do something pass print "thread %s done"%(threading.currentThread().getName()) def main(): print "pid:", os.getpid() signal.signal(signal.SIGINT, handler) thread_list = [] for i in range(5): thread = threading.Thread(target = run) thread_list.append(thread) for thread in thread_list: thread.start() # 注意这里 while True: for thread in thread_list: if thread.isAlive(): break else: break # for thread in thread_list: # thread.join() print "all thread done" if __name__ == '__main__': main()
注意, 在wnidows系统中只能调用 SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, or SIGTERM
作者:thisgf
链接:https://www.jianshu.com/p/c8edab99173d
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
文章来源: Python之signal模块详解