處理python中的訊號

鋼閘門發表於2021-07-04

什麼是訊號

訊號(signal)-- 程式間通訊的一種方式,也可作為一種軟體中斷的方法。一個程式一旦接收到訊號就會打斷原來的程式執行來按照訊號進行處理。

簡化術語,訊號是一個事件,用於中斷執行功能的執行。訊號始終在主Python執行緒中執行。對於訊號,這裡不做詳細介紹。

Python封裝了作業系統的訊號功能的庫 singal 的庫。singal 庫可以使我們在python程式中中實現訊號機制。

Python的訊號處理

首先需要了解Python為什麼要提供 signal Library。訊號庫使我們能夠使用訊號處理程式,以便當接收訊號時都可以執行自定義任務。

Mission:當接收到訊號時執行訊號處理方法

可以通過使用 signal.singal() 函式來實現此功能

Python對訊號的處理

通常情況下Python 訊號處理程式總是會在主 Python 主解析器的主執行緒中執行,即使訊號是在另一個執行緒中接收的。 這意味著訊號不能被用作執行緒間通訊的手段。 你可以改用 threading 模組中的同步原語。

Python訊號處理流程,需要對訊號處理程式(signal handling )簡要說明。signal handling 是一個任務或程式,當檢測到特定訊號時,處理函式需要兩個引數,即訊號id signal number (Linux 中 1-64),與堆疊幀 frame。通過相應訊號啟動對應 signal handlingsignal.signal() 將為訊號分配 處理函式。

如:當執行一個指令碼時,取消,此時是捕獲到一個訊號,可以通過捕獲訊號方式對程式進行非同步的優雅處理。通過將訊號處理程式註冊到應用程式中:

import signal  
import time  

def handler(a, b):  # 定義一個signal handling
    print("Signal Number:", a, " Frame: ", b)  
  
signal.signal(signal.SIGINT, handler)  # 將handle分配給對應訊號
  
while True:  
    print("Press ctrl + c")
    time.sleep(10) 

如果不對對應訊號進行捕獲處理時,python將會丟擲異常。

root@Seal:/mnt/d/pywork/signal# python signal.py
^CTraceback (most recent call last):
  File "signal.py", line 3, in <module>
    while True:
KeyboardInterrupt

訊號列舉

訊號的表現為一個int,Python的訊號庫有對應的訊號列舉成員

其中常用的一般有,

SIGINT control+c

SIGTERM 終止程式 軟體終止訊號

SIGKILL 終止程式 殺死程式

SIGALRM 超時

訊號 說明
SIG_DFL
SIG_IGN 標準訊號處理程式,它將簡單地忽略給定的訊號
SIGABRT
SIGIOT
來自 abort 的中止訊號。
abort 導致異常程式終止。通常由檢測內部錯誤或嚴重破壞約束的庫函式呼叫。例如,如果堆的內部結構被堆溢位損壞,malloc()將呼叫abort()
SIGALRM
SIGVTALRM
SIGPROF
如果你用 setitimer 這一類的報警設定函式設定了一個時限,到達時限時程式會接收到 SIGALRM, SIGVTALRM 或者 SIGPROF。但是這三個訊號量的含義各有不同,SIGALRM 計時的是真實時間,SIGVTALRM計時的是程式使用了多少CPU時間,而 SIGPROF 計時的是程式和代表該程式的核心用了多少時間。
SIGBUS 匯流排發生錯誤時,程式接收到一個SIGBUS訊號。舉例來說,儲存器訪問對齊或者或不存在對應的實體地址都會產生SIGBUS訊號。
SIGCHLD 當子程式終止、被中斷或被中斷後恢復時,SIGCHLD訊號被髮送到程式。該訊號的一個常見用法是指示作業系統在子程式終止後清理其使用的資源,而不顯式呼叫等待系統呼叫。
SIGILL 非法指令。當程式試圖執行非法、格式錯誤、未知或特權指令時,SIGILL訊號被髮送到該程式。
SIGKILL 傳送SIGKILL訊號到一個程式可以使其立即終止(KILL)。與SIGTERM和SIGINT相不同的是,這個訊號不能被捕獲或忽略,接收過程在接收到這個訊號時不能執行任何清理。 以下例外情況適用:
SIGINT 來自鍵盤的中斷 (CTRL + C)。KeyboardInterrupt
SIGPIPE 當一個程式試圖寫入一個沒有連線到另一端程式的管道時,SIGPIPE訊號會被髮送到該程式。
**SIGTERM ** 終結訊號。 KILL -15 |KILL
SIGUSR1
SIGUSR2
使用者自定義訊號
SIGWINCH 終端視窗大小已變化
SIGHUP 在控制終端上檢測到掛起或控制程式的終止。

Reference:[signal-wikipedia](

訊號函式

Python的訊號庫中也有很多常用的函式

signal.alarm(time)

建立一個 SIGALRM 型別的訊號,time為預定的時間,設定為0時取消先前設定的定時器

signal.pause()

可以使程式碼邏輯處理過程睡眠,直到收到訊號,然後呼叫對應的handler。

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的訊號,對USR1做退出操作

signal.setitimer(which, seconds, interval)

which: signal.ITIMER_REAL,signal.ITIMER_VIRTUALsignal.ITIMER_PROF

seconds:多少秒後觸發which。seconds設定為0可以清除which的計時器。

interval:每隔interval秒後觸發一次

os.getpid()

獲得當前執行程式的pid

Windows下訊號的使用

在Linux中,可以通過任何可接受的訊號列舉值作為訊號函式的引數。在Windows中,SIGABRT, SIGFPE, SIGINT, SIGILL, SIGSEGV, SIGTERM, SIGBREAK

當signal handling需要引數怎麼辦

在一些時候,signal handling的操作需要對應主程式傳遞進來一些函式,而在整個專案中執行過程中的變數與 signal handling不處於一個作用域中,而signal.signal() 不能傳遞其他的引數,這個時候可以使用 partial 建立一個閉包來解決這個問題。

例如:

import signal
import os
import sys
import time

from functools import partial

"""
這裡signal frame預設引數需要放到最後
"""
def signal_handler(test_parameter1, test_parameter2, signal_num, frame):
    print "signal {} exit. {} {}".format(signal_num, test_parameter1, test_parameter2)
    sys.exit(1)


a=1
b=2
signal.signal(signal.SIGINT, partial(signal_handler, a, b) )
print('My PID:', os.getpid())

signal.pause()

忽略訊號

signal定義了忽略接收訊號的方法。為了實現訊號的處理,需要使用signal.signal() 將預設的訊號與signal.SIG_IGN 註冊,即可忽略對應的訊號中斷,kill -9 不可忽略 。

import signal
import os
import time

def receiveSignal(signalNumber, frame):
    print('Received:', signalNumber)
    raise SystemExit('Exiting')
    return

if __name__ == '__main__':
    # register the signal to be caught
    signal.signal(signal.SIGUSR1, receiveSignal)

    # register the signal to be ignored
    signal.signal(signal.SIGINT, signal.SIG_IGN)

    # output current process id
    print('My PID is:', os.getpid())

    signal.pause()

常用的訊號

import signal
import os
import time
import sys

def readConfiguration(signalNumber, frame):
    print ('(SIGHUP) reading configuration')
    return

def terminateProcess(signalNumber, frame):
    print ('(SIGTERM) terminating the process')
    sys.exit()

def receiveSignal(signalNumber, frame):
    print('Received:', signalNumber)
    return
	
    signal.signal(signal.SIGHUP, readConfiguration)
    signal.signal(signal.SIGINT, receiveSignal)
    signal.signal(signal.SIGQUIT, receiveSignal)
    signal.signal(signal.SIGILL, receiveSignal)
    signal.signal(signal.SIGTRAP, receiveSignal)
    signal.signal(signal.SIGABRT, receiveSignal)
    signal.signal(signal.SIGBUS, receiveSignal)
    signal.signal(signal.SIGFPE, receiveSignal)
    #signal.signal(signal.SIGKILL, receiveSignal)
    signal.signal(signal.SIGUSR1, receiveSignal)
    signal.signal(signal.SIGSEGV, receiveSignal)
    signal.signal(signal.SIGUSR2, receiveSignal)
    signal.signal(signal.SIGPIPE, receiveSignal)
    signal.signal(signal.SIGALRM, receiveSignal)
    signal.signal(signal.SIGTERM, terminateProcess)

相關文章