signal --- 設置異步事件處理程序?
該模塊提供了在 Python 中使用信號處理程序的機制。
一般規則?
signal.signal() 函數允許定義在接收到信號時執行的自定義處理程序。少量的默認處理程序已經設置: SIGPIPE 被忽略(因此管道和套接字上的寫入錯誤可以報告為普通的 Python 異常)以及如果父進程沒有更改 SIGINT ,則其會被翻譯成 KeyboardInterrupt 異常。
一旦設置,特定信號的處理程序將保持安裝,直到它被顯式重置( Python 模擬 BSD 樣式接口而不管底層實現),但 SIGCHLD 的處理程序除外,它遵循底層實現。
執行 Python 信號處理程序?
Python 信號處理程序不會在低級( C )信號處理程序中執行。相反,低級信號處理程序設置一個標志,告訴 virtual machine 稍后執行相應的 Python 信號處理程序(例如在下一個 bytecode 指令)。這會導致:
捕獲同步錯誤是沒有意義的,例如
SIGFPE或SIGSEGV,它們是由 C 代碼中的無效操作引起的。Python 將從信號處理程序返回到 C 代碼,這可能會再次引發相同的信號,導致 Python 顯然的掛起。 從Python 3.3開始,你可以使用faulthandler模塊來報告同步錯誤。純 C 中實現的長時間運行的計算(例如在大量文本上的正則表達式匹配)可以在任意時間內不間斷地運行,而不管接收到任何信號。計算完成后將調用 Python 信號處理程序。
模塊內容?
在 3.5 版更改: 信號( SIG* ),處理程序( SIG_DFL , SIG_IGN)和 sigmask( SIG_BLOCK , SIG_UNBLOCK , SIG_SETMASK )下面列出的相關常量變成了 enums 。 getsignal() , pthread_sigmask() , sigpending() 和 sigwait() 函數返回人類可讀的 enums 。
在 signal 模塊中定義的變量是:
-
signal.SIG_DFL? 這是兩種標準信號處理選項之一;它只會執行信號的默認函數。 例如,在大多數系統上,對于
SIGQUIT的默認操作是轉儲核心并退出,而對于SIGCHLD的默認操作是簡單地忽略它。
-
signal.SIG_IGN? 這是另一個標準信號處理程序,它將簡單地忽略給定的信號。
-
signal.SIGABRT? 來自 abort(3) 的中止信號。
-
signal.SIGFPE? 浮點異常。 例如除以零。
參見
當除法或求余運算的第二個參數為零時會引發
ZeroDivisionError。
-
signal.SIGILL? 非法指令。
-
signal.SIGINT? 來自鍵盤的中斷 (CTRL + C)。
默認的動作是引發
KeyboardInterrupt。
-
signal.SIGSEGV? 段錯誤:無效的內存引用。
-
signal.SIGTERM? 終結信號。
-
SIG* 所有信號編號都是符號化定義的。 例如,掛起信號被定義為
signal.SIGHUP;變量的名稱與 C 程序中使用的名稱相同,具體見<signal.h>。 'signal()' 的 Unix 手冊頁面列出了現有的信號 (在某些系統上這是 signal(2),在其他系統中此列表則是在 signal(7) 中)。 請注意并非所有系統都會定義相同的信號名稱集;只有系統所定義的名稱才會由此模塊來定義。
-
signal.NSIG? 比最高信號數多一。
-
signal.ITIMER_VIRTUAL? 僅在進程執行時遞減間隔計時器,并在到期時發送 SIGVTALRM 。
-
signal.ITIMER_PROF? 當進程執行時以及當系統替進程執行時都會減小間隔計時器。 這個計時器與 ITIMER_VIRTUAL 相配結,通常被用于分析應用程序在用戶和內核空間中花費的時間。 SIGPROF 會在超期時被發送。
-
signal.SIG_BLOCK? pthread_sigmask()的 how 形參的一個可能的值,表明信號將會被阻塞。3.3 新版功能.
-
signal.SIG_UNBLOCK? pthread_sigmask()的 how 形參的是個可能的值,表明信號將被解除阻塞。3.3 新版功能.
-
signal.SIG_SETMASK? pthread_sigmask()的 how 形參的一個可能的值,表明信號掩碼將要被替換。3.3 新版功能.
signal 模塊定義了一個異常:
-
exception
signal.ItimerError? 作為來自下層
setitimer()或getitimer()實現錯誤的信號被引發。 如果將無效的定時器或負的時間值傳給setitimer()就導致這個錯誤。 此錯誤是OSError的子類型。
signal 模塊定義了以下函數:
-
signal.alarm(time)? 如果 time 值非零,則此函數將要求將一個
SIGALRM信號在 time 秒內發往進程。 任何在之前排入計劃的警報都會被取消(在任何時刻都只能有一個警報被排入計劃)。 后續的返回值將是任何之前設置的警報被傳入之前的秒數。 如果 time 值為零,則不會將任何警報排入計劃,并且任何已排入計劃的警報都會被取消。 如果返回值為零,則目前沒有任何警報被排入計劃。可用性: Unix。 更多信息請參見手冊頁面 alarm(2)。
-
signal.getsignal(signalnum)? 返回當前用于信號 signalnum 的信號處理程序。 返回值可以是一個 Python 可調用對象,或是特殊值
signal.SIG_IGN,signal.SIG_DFL或None之一。 在這里,signal.SIG_IGN表示信號在之前被忽略,signal.SIG_DFL表示之前在使用默認的信號處理方式,而None表示之前的信號處理程序未由 Python 安裝。
-
signal.pause()? 使進程休眠直至接收到一個信號;然后將會調用適當的處理程序。 返回空值。
可用性: Unix。 更多信息請參見手冊頁面 signal(2)。
另請參閱
sigwait(),sigwaitinfo(),sigtimedwait()和sigpending()。
-
signal.pthread_kill(thread_id, signalnum)? Send the signal signalnum to the thread thread_id, another thread in the same process as the caller. The target thread can be executing any code (Python or not). However, if the target thread is executing the Python interpreter, the Python signal handlers will be executed by the main thread. Therefore, the only point of sending a signal to a particular Python thread would be to force a running system call to fail with
InterruptedError.使用
threading.get_ident()或threading.Thread對象的ident屬性為 thread_id 獲取合適的值。如果 signalnum 為 0,則不會發送信號,但仍然會執行錯誤檢測;這可被用來檢測目標線程是否仍在運行。
可用性: Unix。 更多信息請參見手冊頁面 pthread_kill(3)。
另請參閱
os.kill()。3.3 新版功能.
-
signal.pthread_sigmask(how, mask)? 獲取和/或修改調用方線程的信號掩碼。 信號掩碼是一組傳送過程目前為調用者而阻塞的信號集。 返回舊的信號掩碼作為一組信號。
該調用的行為取決于 how 的值,具體見下。
SIG_BLOCK: 被阻塞的信號集是當前集與 mask 參數的并集。SIG_UNBLOCK: mask 中的信號會從當前已阻塞信號集中被移除。 允許嘗試取消對一個非阻塞信號的阻塞。SIG_SETMASK: 已阻塞信號集會被設為 mask 參數的值。
mask is a set of signal numbers (e.g. {
signal.SIGINT,signal.SIGTERM}). Userange(1, signal.NSIG)for a full mask including all signals.例如,
signal.pthread_sigmask(signal.SIG_BLOCK, [])會讀取調用方線程的信號掩碼。SIGKILL和SIGSTOP不能被阻塞。可用性: Unix。 更多信息請參見手冊頁面 sigprocmask(3) 和 pthread_sigmask(3)。
另請參閱
pause(),sigpending()和sigwait()。3.3 新版功能.
-
signal.setitimer(which, seconds, interval=0.0)? 設置由 which 指明的給定間隔計時器 (
signal.ITIMER_REAL,signal.ITIMER_VIRTUAL或signal.ITIMER_PROF之一) 在 seconds 秒 (接受浮點數值,為與alarm()之差) 之后開始并在每 interval 秒間隔時 (如果 interval 不為零) 啟動。 由 which 指明的間隔計時器可通過將 seconds 設為零來清空。當一個間隔計時器啟動時,會有信號發送至進程。 所發送的具體信號取決于所使用的計時器;
signal.ITIMER_REAL將發送SIGALRM,signal.ITIMER_VIRTUAL將發送SIGVTALRM, 而signal.ITIMER_PROF將發送SIGPROF.原有的值會以元組: (delay, interval) 的形式被返回。
嘗試傳入無效的計時器將導致
ItimerError。可用性: Unix。
-
signal.set_wakeup_fd(fd, *, warn_on_full_buffer=True)? 將喚醒文件描述符設為 fd。 當接收到信號時,會將信號編號以單個字節的形式寫入 fd。 這可被其它庫用來喚醒一次 poll 或 select 調用,以允許該信號被完全地處理。
原有的喚醒 fd 會被返回(或者如果未啟用文件描述符喚醒則返回 -1)。 如果 fd 為 -1,文件描述符喚醒會被禁用。 如果不為 -1,則 fd 必須為非阻塞型。 需要由庫來負責在重新調用 poll 或 select 之前從 fd 移除任何字節數據。
When threads are enabled, this function can only be called from the main thread; attempting to call it from other threads will cause a
ValueErrorexception to be raised.使用此函數有兩種通常的方式。 在兩種方式下,當有信號到達時你都是用 fd 來喚醒,但之后它們在確定達到的一個或多個信號 which 時存在差異。
在第一種方式下,我們從 fd 的緩沖區讀取數據,這些字節值會給你信號編號。 這種方式很簡單,但在少數情況下會發生問題:通常 fd 將有緩沖區空間大小限制,如果信號到達得太多且太快,緩沖區可能會爆滿,有些信號可能丟失。 如果你使用此方式,則你應當設置
warn_on_full_buffer=True,當信號丟失時這至少能將警告消息打印到 stderr。在第二種方式下,我們 只會 將喚醒 fd 用于喚醒,而忽略實際的字節值。 在此情況下,我們所關心的只有 fd 的緩沖區為空還是不為空;爆滿的緩沖區完全不會導致問題。 如果你使用此方式,則你應當設置
warn_on_full_buffer=False,這樣你的用戶就不會被虛假的警告消息所迷惑。在 3.5 版更改: 在 Windows 上,此函數現在也支持套接字處理。
在 3.7 版更改: 添加了
warn_on_full_buffer形參。
-
signal.siginterrupt(signalnum, flag)? 更改系統調用重啟行為:如果 flag 為
False,系統調用將在被信號 signalnum 中斷時重啟,否則系統調用將被中斷。 返回空值。可用性: Unix。 更多信息請參見手冊頁面 siginterrupt(3)。
請注意用
signal()安裝信號處理程序將重啟行為重置為可通過顯式調用siginterrupt()并為給定信號的 flag 設置真值來實現中斷。
-
signal.signal(signalnum, handler)? 將信號 signalnum 的處理程序設為函數 handler。 handler 可以為接受兩個參數(見下)的 Python 可調用對象,或者為特殊值
signal.SIG_IGN或signal.SIG_DFL之一。 之前的信號處理程序將被返回(參見上文getsignal()的描述)。 (更多信息請參閱 Unix 手冊頁面 signal(2)。)When threads are enabled, this function can only be called from the main thread; attempting to call it from other threads will cause a
ValueErrorexception to be raised.handler 將附帶兩個參數調用:信號編號和當前堆棧幀 (
None或一個幀對象;有關幀對象的描述請參閱 類型層級結構描述 或者參閱inspect模塊中的屬性描述)。在 Windows 上,
signal()調用只能附帶SIGABRT,SIGFPE,SIGILL,SIGINT,SIGSEGV,SIGTERM或SIGBREAK。 任何其他值都將引發ValueError。 請注意不是所有系統都定義了同樣的信號名稱集合;如果一個信號名稱未被定義為SIG*模塊層級常量則將引發AttributeError。
-
signal.sigpending()? 檢查正在等待傳送給調用方線程的信號集合(即在阻塞期間被引發的信號)。 返回正在等待的信號集合。
可用性: Unix。 更多信息請參見手冊頁面 sigpending(2)。
另請參閱
pause(),pthread_sigmask()和sigwait()。3.3 新版功能.
-
signal.sigwait(sigset)? 掛起調用方線程的執行直到信號集合 sigset 中指定的信號之一被傳送。 此函數會接受該信號(將其從等待信號列表中移除),并返回信號編號。
可用性: Unix。 更多信息請參見手冊頁面 sigwait(3)。
另請參閱
pause(),pthread_sigmask(),sigpending(),sigwaitinfo()和sigtimedwait()。3.3 新版功能.
-
signal.sigwaitinfo(sigset)? 掛起調用方線程的執行直到信號集合 sigset 中指定的信號之一被傳送。 此函數會接受該信號并將其從等待信號列表中移除。 如果 sigset 中的信號之一已經在等待調用方線程,此函數將立即返回并附帶有關該信號的信息。 被傳送信號的信號處理程序不會被調用。 如果該函數被某個不在 sigset 中的信號中斷則會引發
InterruptedError。返回值是一個代表
siginfo_t結構體所包含數據的對象,具體為:si_signo,si_code,si_errno,si_pid,si_uid,si_status,si_band。可用性: Unix。 更多信息請參見手冊頁面 sigwaitinfo(2)。
另請參閱
pause(),sigwait()和sigtimedwait()。3.3 新版功能.
在 3.5 版更改: 當被某個 不在 sigset 中的信號中斷時本函數將結束并且信號處理程序不會引發異常 (其理由參見 PEP 475)。
-
signal.sigtimedwait(sigset, timeout)? 類似于
sigwaitinfo(),但會接受一個額外的 timeout 參數來指定超時限制。 如果將 timeout 指定為0,則會執行輪詢。 如果發生超時則返回None。可用性: Unix。 更多信息請參見手冊頁面 sigtimedwait(2)。
另請參閱
pause(),sigwait()和sigwaitinfo()。3.3 新版功能.
在 3.5 版更改: 現在當此函數被某個不在 sigset 中的信號中斷時將以計算出的 timeout 進行重試并且信號處理程序不會引發異常(請參閱 PEP 475 了解其理由)。
示例?
這是一個最小示例程序。 它使用 alarm() 函數來限制等待打開一個文件所花費的時間;這在文件為無法開啟的串行設備時會很有用處,此情況通常會導致 os.open() 無限期地掛起。 解決辦法是在打開文件之前設置 5 秒鐘的 alarm;如果操作耗時過長,將會發送 alarm 信號,并且處理程序會引發一個異常。
import signal, os
def handler(signum, frame):
print('Signal handler called with signal', signum)
raise OSError("Couldn't open device!")
# Set the signal handler and a 5-second alarm
signal.signal(signal.SIGALRM, handler)
signal.alarm(5)
# This open() may hang indefinitely
fd = os.open('/dev/ttyS0', os.O_RDWR)
signal.alarm(0) # Disable the alarm
對于 SIGPIPE 的說明?
將你的程序用管道輸出到工具例如 head(1) 將會導致 SIGPIPE 信號在其標準輸出的接收方提前關閉時被發送到你的進程。 這將引發一個異常例如 BrokenPipeError: [Errno 32] Broken pipe。 要處理這種情況,請對你的入口點進行包裝以捕獲此異常,如下所示:
import os
import sys
def main():
try:
# simulate large output (your code replaces this loop)
for x in range(10000):
print("y")
# flush output here to force SIGPIPE to be triggered
# while inside this try block.
sys.stdout.flush()
except BrokenPipeError:
# Python flushes standard streams on exit; redirect remaining output
# to devnull to avoid another BrokenPipeError at shutdown
devnull = os.open(os.devnull, os.O_WRONLY)
os.dup2(devnull, sys.stdout.fileno())
sys.exit(1) # Python exits with error code 1 on EPIPE
if __name__ == '__main__':
main()
不要將 SIGPIPE 的處置方式設為 SIG_DFL 以避免 BrokenPipeError。 這樣做還會在你的程序所寫入的任何套接字連接中斷時導致你的程序異常退出。
