先簡單說一下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