先簡單說一下Signal是啥.(如果想直接使用可以不看)
Signal翻譯過來中文就是信號- -
當(dāng)然, 本身他就是Linux系統(tǒng)編程中非常重要的概念, 信號機(jī)制是進(jìn)程之間傳遞消息的一種機(jī)制,
其全稱為軟中斷信號
作用是通知進(jìn)程發(fā)生了異步事件。進(jìn)程之間可以調(diào)用系統(tǒng)來傳遞信號, 本身內(nèi)核也可以發(fā)送信號給進(jìn)程, 告訴該進(jìn)程發(fā)生了某個事件.
注意,信號只是用來通知某進(jìn)程發(fā)生了什么事件,并不給該進(jìn)程傳遞任何數(shù)據(jù)。
接收信號的進(jìn)程對不同的信號有三種處理方法
- 指定處理函數(shù)
- 忽略
- 根據(jù)系統(tǒng)默認(rèn)值處理, 大部分信號的默認(rèn)處理是終止進(jìn)程
然后就是一大段類型了..
Linux系統(tǒng)有兩大類信號
- POSIX標(biāo)準(zhǔn)的規(guī)則信號(regular signal 1-31編號)
- 實時信號(real-time signal 32-63)
規(guī)則信號
| 信號編號 | 名稱 | 默認(rèn)動作 | 說明 |
|---|---|---|---|
| 1 | SIGHUP | 終止 | 終止控制終端或進(jìn)程 |
| 2 | SIGINT | 終止 | 由鍵盤引起的終端(Ctrl-c) |
| 3 | SIGQUIT | dump | 控制終端發(fā)送給進(jìn)程的信號, 鍵盤產(chǎn)生的退出(Ctrl-\), |
| 4 | GIGILL | dusmp | 非法指令引起 |
| 5 | SIGTRAP | dump | debug中斷 |
| 6 | SIGABRT/SIGIOT | dump | 異常中止 |
| 7 | SIGBUS/SIGEMT | dump | 總線異常/EMT指令 |
| 8 | SIGFPE | dump | 浮點運(yùn)算溢出 |
| 9 | SIGKILL | 終止 | 強(qiáng)制殺死進(jìn)程(大招, 進(jìn)程不可捕獲) |
| 10 | SIGUSR1 | 終止 | 用戶信號, 進(jìn)程可自定義用途 |
| 11 | SIGSEGV | dump | 非法內(nèi)存地址引起 |
| 12 | SIGUSR2 | 終止 | 用戶信號, 進(jìn)程可自定義用途 |
| 13 | SIGPIPE | 終止 | 向某個沒有讀取的管道中寫入數(shù)據(jù) |
| 14 | SIGALRM | 終止 | 時鐘中斷(鬧鐘) |
| 15 | SIGTERM | 終止 | 進(jìn)程終止(進(jìn)程可捕獲) |
| 16 | SIGSTKFLT | 終止 | 協(xié)處理器棧錯誤 |
| 17 | SIGCHLD | 忽略 | 子進(jìn)程退出或中斷 |
| 18 | SIGCONT | 繼續(xù) | 如進(jìn)程停止?fàn)顟B(tài)則開始運(yùn)行 |
| 19 | SIGSTOP | 停止 | 停止進(jìn)程運(yùn)行 |
| 20 | SIGSTP | 停止 | 鍵盤產(chǎn)生的停止 |
| 21 | SIGTTIN | 停止 | 后臺進(jìn)程請求輸入 |
| 22 | SIGTTOU | 停止 | 后臺進(jìn)程請求輸出 |
| 23 | SIGURG | 忽略 | socket發(fā)送緊急情況 |
| 24 | SIGXCPU | dump | CPU時間限制被打破 |
| 25 | SIGXFSZ | dump | 文件大小限制被打破 |
| 26 | SIGVTALRM | 終止 | 虛擬定時時鐘 |
| 27 | SIGPROF | 終止 | profile timer clock |
| 28 | SIGWINCH | 忽略 | 窗口尺寸調(diào)整 |
| 29 | SIGIO/SIGPOLL | 終止 | I/O可用 |
| 30 | SIGPWR | 終止 | 電源異常 |
| 31 | SIGSYS/SYSUNUSED | dump | 系統(tǒng)調(diào)用異常 |
注意: 由于不同系統(tǒng)中同一個數(shù)值對應(yīng)的信號類型不一樣, 所以最好使用信號名稱.
信號的數(shù)值越小, 優(yōu)先級越高.
OK, 現(xiàn)在來說說Python中的處理
先列幾個常用的信號:
| 編號 | 信號名稱 | 說明 |
|---|---|---|
| 2 | SIGINT | 當(dāng)按下鍵盤(Ctrl-c)組合鍵時進(jìn)程就會收到這個信號 |
| 15 | SIGTERM | 當(dāng)用戶輸入 kill sigterm pid. 對應(yīng)的進(jìn)程就會收到這個信號. 這個信號進(jìn)程是可以捕獲并指定函數(shù)處理, 例如做一下程序清理等工作. 甚至忽視這個信號 |
| 9 | SIGKILL | 強(qiáng)制殺死進(jìn)程, 這個信號進(jìn)程無法忽視, 直接在系統(tǒng)層面把進(jìn)程殺掉. 所以在Python中他的不能監(jiān)聽的 |
| 14 | SIGALRM | 鬧鐘信號 |
去碼
先來一個例子
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
監(jiān)聽了SIGINT信號, 當(dāng)程序在運(yùn)行的時候同時按下鍵盤 Ctrl+c 就會輸出
收到信號 2 <frame object at 0x00000000021DD048>
handler方法的兩個參數(shù)分別是 信號編號, 程序幀
"""
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, 所以這里程序就會結(jié)束
while True: # 改成 while 效果會好點
pass
if __name__ == '__main__':
main()
再看看SIGTERM的效果
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
當(dāng)我們運(yùn)行該程序時因為 while True 所以會持續(xù)的運(yùn)行.
這里監(jiān)聽的是 SIGTERM 信號, 所以當(dāng)我們在終端輸入 kill pid (linux kill
默認(rèn)是發(fā)送SIGTERM)時,
程序就會輸出: 收到信號 15 <frame object at 0x7ff695738050> 0
當(dāng)超過3次時就強(qiáng)制把自己殺死.
所以 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不能被監(jiān)聽.
signal.signal(signal.SIGKILL, handler)
# 這里系統(tǒng)會直接跑錯 AttributeError: 'module' object has no attribute 'SIGKILL'
最后來一個實際運(yùn)用的例子
在python2.x的版本, 線程有個bug, 在join的時候不能接收信號
詳解見:https://bugs.python.org/issue1167930
所以如果我們運(yùn)行以下代碼
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
這里雖然我們監(jiān)聽了 SIGINT 信號, 但是當(dāng)我們按下Ctrl-c時程序并沒有任何輸出. 還是要等線程運(yùn)行完成程序才退出.
"""
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) # 我瘋起來連自己都?xì)? 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 的方式來代替, 然后在主進(jìn)程判斷線程的存活狀態(tài). 這樣既能持續(xù)的運(yùn)行線程又能根據(jù)需求來隨時中斷
"""
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 # 停止運(yùn)行線程
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系統(tǒng)中只能調(diào)用 SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, or SIGTERM