Python和Signal

Posted 风-fmgao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python和Signal相关的知识,希望对你有一定的参考价值。

 

先简单说一下Signal是啥.(如果想直接使用可以不看)

Signal翻译过来中文就是信号- -
当然, 本身他就是Linux系统编程中非常重要的概念, 信号机制是进程之间传递消息的一种机制,

其全称为软中断信号
作用是通知进程发生了异步事件。进程之间可以调用系统来传递信号, 本身内核也可以发送信号给进程, 告诉该进程发生了某个事件.

注意,信号只是用来通知某进程发生了什么事件,并不给该进程传递任何数据。

接收信号的进程对不同的信号有三种处理方法

  1. 指定处理函数
  2. 忽略
  3. 根据系统默认值处理, 大部分信号的默认处理是终止进程

然后就是一大段类型了..
Linux系统有两大类信号

  1. POSIX标准的规则信号(regular signal 1-31编号)
  2. 实时信号(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







以上是关于Python和Signal的主要内容,如果未能解决你的问题,请参考以下文章

python signal信号

Python和Signal

常用python日期日志获取内容循环的代码片段

python 代码片段和解决方案

Python模块之信号(signal)

python 之信号Signal