signal
--- 設置異步事件處理程序?
該模塊提供了在 Python 中使用信號處理程序的機制。
一般規(guī)則?
signal.signal()
函數(shù)允許定義在接收到信號時執(zhí)行的自定義處理程序。少量的默認處理程序已經(jīng)設置: SIGPIPE
被忽略(因此管道和套接字上的寫入錯誤可以報告為普通的 Python 異常)以及如果父進程沒有更改 SIGINT
,則其會被翻譯成 KeyboardInterrupt
異常。
一旦設置,特定信號的處理程序?qū)⒈3职惭b,直到它被顯式重置( Python 模擬 BSD 樣式接口而不管底層實現(xiàn)),但 SIGCHLD
的處理程序除外,它遵循底層實現(xiàn)。
執(zhí)行 Python 信號處理程序?
Python 信號處理程序不會在低級( C )信號處理程序中執(zhí)行。相反,低級信號處理程序設置一個標志,告訴 virtual machine 稍后執(zhí)行相應的 Python 信號處理程序(例如在下一個 bytecode 指令)。這會導致:
捕獲同步錯誤是沒有意義的,例如
SIGFPE
或SIGSEGV
,它們是由 C 代碼中的無效操作引起的。Python 將從信號處理程序返回到 C 代碼,這可能會再次引發(fā)相同的信號,導致 Python 顯然的掛起。 從Python 3.3開始,你可以使用faulthandler
模塊來報告同步錯誤。純 C 中實現(xiàn)的長時間運行的計算(例如在大量文本上的正則表達式匹配)可以在任意時間內(nèi)不間斷地運行,而不管接收到任何信號。計算完成后將調(diào)用 Python 信號處理程序。
If the handler raises an exception, it will be raised "out of thin air" in the main thread. See the note below for a discussion.
信號與線程?
Python 信號處理程序總是會在主 Python 主解釋器的主線程中執(zhí)行,即使信號是在另一個線程中接收的。 這意味著信號不能被用作線程間通信的手段。 你可以改用 threading
模塊中的同步原語。
此外,只有主解釋器的主線程才被允許設置新的信號處理程序。
模塊內(nèi)容?
在 3.5 版更改: signal (SIG*), handler (SIG_DFL
, SIG_IGN
) and sigmask
(SIG_BLOCK
, SIG_UNBLOCK
, SIG_SETMASK
)
related constants listed below were turned into
enums
(Signals
, Handlers
and Sigmasks
respectively).
getsignal()
, pthread_sigmask()
, sigpending()
and
sigwait()
functions return human-readable
enums
as Signals
objects.
The signal module defines three enums:
- class signal.Signals?
enum.IntEnum
collection of SIG* constants and the CTRL_* constants.3.5 新版功能.
- class signal.Handlers?
enum.IntEnum
collection the constantsSIG_DFL
andSIG_IGN
.3.5 新版功能.
- class signal.Sigmasks?
enum.IntEnum
collection the constantsSIG_BLOCK
,SIG_UNBLOCK
andSIG_SETMASK
.Availability: Unix. See the man page sigprocmask(3) and pthread_sigmask(3) for further information.
3.5 新版功能.
在 signal
模塊中定義的變量是:
- signal.SIG_DFL?
這是兩種標準信號處理選項之一;它只會執(zhí)行信號的默認函數(shù)。 例如,在大多數(shù)系統(tǒng)上,對于
SIGQUIT
的默認操作是轉(zhuǎn)儲核心并退出,而對于SIGCHLD
的默認操作是簡單地忽略它。
- signal.SIG_IGN?
這是另一個標準信號處理程序,它將簡單地忽略給定的信號。
- signal.SIGFPE?
浮點異常。 例如除以零。
參見
當除法或求余運算的第二個參數(shù)為零時會引發(fā)
ZeroDivisionError
。
- signal.SIGILL?
非法指令。
- signal.SIGINT?
來自鍵盤的中斷 (CTRL + C)。
默認的動作是引發(fā)
KeyboardInterrupt
。
- signal.SIGSEGV?
段錯誤:無效的內(nèi)存引用。
- signal.SIGSTKFLT?
Stack fault on coprocessor. The Linux kernel does not raise this signal: it can only be raised in user space.
Availability: Linux, on architectures where the signal is available. See the man page signal(7) for further information.
3.11 新版功能.
- signal.SIGTERM?
終結(jié)信號。
- SIG*
所有信號編號都是符號化定義的。 例如,掛起信號被定義為
signal.SIGHUP
;變量的名稱與 C 程序中使用的名稱相同,具體見<signal.h>
。 'signal()
' 的 Unix 手冊頁面列出了現(xiàn)有的信號 (在某些系統(tǒng)上這是 signal(2),在其他系統(tǒng)中此列表則是在 signal(7) 中)。 請注意并非所有系統(tǒng)都會定義相同的信號名稱集;只有系統(tǒng)所定義的名稱才會由此模塊來定義。
- signal.NSIG?
One more than the number of the highest signal number. Use
valid_signals()
to get valid signal numbers.
- signal.ITIMER_VIRTUAL?
僅在進程執(zhí)行時遞減間隔計時器,并在到期時發(fā)送 SIGVTALRM 。
- signal.ITIMER_PROF?
當進程執(zhí)行時以及當系統(tǒng)替進程執(zhí)行時都會減小間隔計時器。 這個計時器與 ITIMER_VIRTUAL 相配結(jié),通常被用于分析應用程序在用戶和內(nèi)核空間中花費的時間。 SIGPROF 會在超期時被發(fā)送。
- 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()
實現(xiàn)錯誤的信號被引發(fā)。 如果將無效的定時器或負的時間值傳給setitimer()
就導致這個錯誤。 此錯誤是OSError
的子類型。
signal
模塊定義了以下函數(shù):
- signal.alarm(time)?
如果 time 值非零,則此函數(shù)將要求將一個
SIGALRM
信號在 time 秒內(nèi)發(fā)往進程。 任何在之前排入計劃的警報都會被取消(在任何時刻都只能有一個警報被排入計劃)。 后續(xù)的返回值將是任何之前設置的警報被傳入之前的秒數(shù)。 如果 time 值為零,則不會將任何警報排入計劃,并且任何已排入計劃的警報都會被取消。 如果返回值為零,則目前沒有任何警報被排入計劃。
- signal.getsignal(signalnum)?
返回當前用于信號 signalnum 的信號處理程序。 返回值可以是一個 Python 可調(diào)用對象,或是特殊值
signal.SIG_IGN
,signal.SIG_DFL
或None
之一。 在這里,signal.SIG_IGN
表示信號在之前被忽略,signal.SIG_DFL
表示之前在使用默認的信號處理方式,而None
表示之前的信號處理程序未由 Python 安裝。
- signal.strsignal(signalnum)?
返回信號 signalnum 的系統(tǒng)描述,例如 "Interrupt", "Segmentation fault" 等等。 如果信號無法被識別則返回
None
。3.8 新版功能.
- signal.valid_signals()?
返回本平臺上的有效信號編號集。 這可能會少于
range(1, NSIG)
,如果某些信號被系統(tǒng)保留作為內(nèi)部使用的話。3.8 新版功能.
- signal.pause()?
使進程休眠直至接收到一個信號;然后將會調(diào)用適當?shù)奶幚沓绦颉?返回空值。
可用性: Unix。 更多信息請參見手冊頁面 signal(2)。
另請參閱
sigwait()
,sigwaitinfo()
,sigtimedwait()
和sigpending()
。
- signal.raise_signal(signum)?
向調(diào)用方進程發(fā)送一個信號。 返回空值。
3.8 新版功能.
- signal.pidfd_send_signal(pidfd, sig, siginfo=None, flags=0)?
發(fā)送信號 sig 到文件描述符 pidfd 所指向的進程。 Python 目前不支持 siginfo 形參;它必須為
None
。 提供 flags 參數(shù)是為了將來擴展;當前未定義旗標值。更多信息請參閱 pidfd_send_signal(2) 手冊頁面。
可用性: Linux 5.1+
3.9 新版功能.
- signal.pthread_kill(thread_id, signalnum)?
將信號 signalnum 發(fā)送至與調(diào)用者在同一進程中另一線程 thread_id。 目標線程可被用于執(zhí)行任何代碼(Python或其它)。 但是,如果目標線程是在執(zhí)行 Python 解釋器,則 Python 信號處理程序?qū)?由主解釋器的主線程來執(zhí)行。 因此,將信號發(fā)送給特定 Python 線程的唯一作用在于強制讓一個正在運行的系統(tǒng)調(diào)用失敗并拋出
InterruptedError
。使用
threading.get_ident()
或threading.Thread
對象的ident
屬性為 thread_id 獲取合適的值。如果 signalnum 為 0,則不會發(fā)送信號,但仍然會執(zhí)行錯誤檢測;這可被用來檢測目標線程是否仍在運行。
引發(fā)一個 審計事件
signal.pthread_kill
,附帶參數(shù)thread_id
,signalnum
。可用性: Unix。 更多信息請參見手冊頁面 pthread_kill(3)。
另請參閱
os.kill()
。3.3 新版功能.
- signal.pthread_sigmask(how, mask)?
獲取和/或修改調(diào)用方線程的信號掩碼。 信號掩碼是一組傳送過程目前為調(diào)用者而阻塞的信號集。 返回舊的信號掩碼作為一組信號。
該調(diào)用的行為取決于 how 的值,具體見下。
SIG_BLOCK
: 被阻塞信號集是當前集與 mask 參數(shù)的并集。SIG_UNBLOCK
: mask 中的信號會從當前已阻塞信號集中被移除。 允許嘗試取消對一個非阻塞信號的阻塞。SIG_SETMASK
: 已阻塞信號集會被設為 mask 參數(shù)的值。
mask 是一個信號編號集合 (例如 {
signal.SIGINT
,signal.SIGTERM
})。 請使用valid_signals()
表示包含所有信號的完全掩碼。例如,
signal.pthread_sigmask(signal.SIG_BLOCK, [])
會讀取調(diào)用方線程的信號掩碼。SIGKILL
和SIGSTOP
不能被阻塞。可用性: Unix。 更多信息請參見手冊頁面 sigprocmask(2) 和 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 秒 (接受浮點數(shù)值,為與alarm()
之差) 之后開始并在每 interval 秒間隔時 (如果 interval 不為零) 啟動。 由 which 指明的間隔計時器可通過將 seconds 設為零來清空。當一個間隔計時器啟動時,會有信號發(fā)送至進程。 所發(fā)送的具體信號取決于所使用的計時器;
signal.ITIMER_REAL
將發(fā)送SIGALRM
,signal.ITIMER_VIRTUAL
將發(fā)送SIGVTALRM
, 而signal.ITIMER_PROF
將發(fā)送SIGPROF
.原有的值會以元組: (delay, interval) 的形式被返回。
嘗試傳入無效的計時器將導致
ItimerError
。可用性: Unix。
- signal.set_wakeup_fd(fd, *, warn_on_full_buffer=True)?
將喚醒文件描述符設為 fd。 當接收到信號時,會將信號編號以單個字節(jié)的形式寫入 fd。 這可被其它庫用來喚醒一次 poll 或 select 調(diào)用,以允許該信號被完全地處理。
原有的喚醒 fd 會被返回(或者如果未啟用文件描述符喚醒則返回 -1)。 如果 fd 為 -1,文件描述符喚醒會被禁用。 如果不為 -1,則 fd 必須為非阻塞型。 需要由庫來負責在重新調(diào)用 poll 或 select 之前從 fd 移除任何字節(jié)數(shù)據(jù)。
當啟用線程用時,此函數(shù)只能從 主解釋器的主線程 被調(diào)用;嘗試從另一線程調(diào)用它將導致
ValueError
異常被引發(fā)。使用此函數(shù)有兩種通常的方式。 在兩種方式下,當有信號到達時你都是用 fd 來喚醒,但之后它們在確定達到的一個或多個信號 which 時存在差異。
在第一種方式下,我們從 fd 的緩沖區(qū)讀取數(shù)據(jù),這些字節(jié)值會給你信號編號。 這種方式很簡單,但在少數(shù)情況下會發(fā)生問題:通常 fd 將有緩沖區(qū)空間大小限制,如果信號到達得太多且太快,緩沖區(qū)可能會爆滿,有些信號可能丟失。 如果你使用此方式,則你應當設置
warn_on_full_buffer=True
,當信號丟失時這至少能將警告消息打印到 stderr。在第二種方式下,我們 只會 將喚醒 fd 用于喚醒,而忽略實際的字節(jié)值。 在此情況下,我們所關(guān)心的只有 fd 的緩沖區(qū)為空還是不為空;爆滿的緩沖區(qū)完全不會導致問題。 如果你使用此方式,則你應當設置
warn_on_full_buffer=False
,這樣你的用戶就不會被虛假的警告消息所迷惑。在 3.5 版更改: 在 Windows 上,此函數(shù)現(xiàn)在也支持套接字處理。
在 3.7 版更改: 添加了
warn_on_full_buffer
形參。
- signal.siginterrupt(signalnum, flag)?
更改系統(tǒng)調(diào)用重啟行為:如果 flag 為
False
,系統(tǒng)調(diào)用將在被信號 signalnum 中斷時重啟,否則系統(tǒng)調(diào)用將被中斷。 返回空值。可用性: Unix。 更多信息請參見手冊頁面 siginterrupt(3)。
請注意用
signal()
安裝信號處理程序?qū)⒅貑⑿袨橹刂脼榭赏ㄟ^顯式調(diào)用siginterrupt()
并為給定信號的 flag 設置真值來實現(xiàn)中斷。
- signal.signal(signalnum, handler)?
將信號 signalnum 的處理程序設為函數(shù) handler。 handler 可以為接受兩個參數(shù)(見下)的 Python 可調(diào)用對象,或者為特殊值
signal.SIG_IGN
或signal.SIG_DFL
之一。 之前的信號處理程序?qū)⒈环祷兀▍⒁娚衔?getsignal()
的描述)。 (更多信息請參閱 Unix 手冊頁面 signal(2)。)當啟用線程用時,此函數(shù)只能從 主解釋器的主線程 被調(diào)用;嘗試從另一線程調(diào)用它將導致
ValueError
異常被引發(fā)。handler 將附帶兩個參數(shù)調(diào)用:信號編號和當前堆棧幀 (
None
或一個幀對象;有關(guān)幀對象的描述請參閱 類型層級結(jié)構(gòu)描述 或者參閱inspect
模塊中的屬性描述)。在 Windows 上,
signal()
調(diào)用只能附帶SIGABRT
,SIGFPE
,SIGILL
,SIGINT
,SIGSEGV
,SIGTERM
或SIGBREAK
。 任何其他值都將引發(fā)ValueError
。 請注意不是所有系統(tǒng)都定義了同樣的信號名稱集合;如果一個信號名稱未被定義為SIG*
模塊層級常量則將引發(fā)AttributeError
。
- signal.sigpending()?
檢查正在等待傳送給調(diào)用方線程的信號集合(即在阻塞期間被引發(fā)的信號)。 返回正在等待的信號集合。
可用性: Unix。 更多信息請參見手冊頁面 sigpending(2)。
另請參閱
pause()
,pthread_sigmask()
和sigwait()
。3.3 新版功能.
- signal.sigwait(sigset)?
掛起調(diào)用方線程的執(zhí)行直到信號集合 sigset 中指定的信號之一被傳送。 此函數(shù)會接受該信號(將其從等待信號列表中移除),并返回信號編號。
可用性: Unix。 更多信息請參見手冊頁面 sigwait(3)。
另請參閱
pause()
,pthread_sigmask()
,sigpending()
,sigwaitinfo()
和sigtimedwait()
。3.3 新版功能.
- signal.sigwaitinfo(sigset)?
掛起調(diào)用方線程的執(zhí)行直到信號集合 sigset 中指定的信號之一被傳送。 此函數(shù)會接受該信號并將其從等待信號列表中移除。 如果 sigset 中的信號之一已經(jīng)在等待調(diào)用方線程,此函數(shù)將立即返回并附帶有關(guān)該信號的信息。 被傳送信號的信號處理程序不會被調(diào)用。 如果該函數(shù)被某個不在 sigset 中的信號中斷則會引發(fā)
InterruptedError
。返回值是一個代表
siginfo_t
結(jié)構(gòu)體所包含數(shù)據(jù)的對象,具體為: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 中的信號中斷時本函數(shù)將進行重試并且信號處理程序不會引發(fā)異常(請參閱 PEP 475 了解其理由)。
- signal.sigtimedwait(sigset, timeout)?
類似于
sigwaitinfo()
,但會接受一個額外的 timeout 參數(shù)來指定超時限制。 如果將 timeout 指定為0
,則會執(zhí)行輪詢。 如果發(fā)生超時則返回None
。可用性: Unix。 更多信息請參見手冊頁面 sigtimedwait(2)。
另請參閱
pause()
,sigwait()
和sigwaitinfo()
。3.3 新版功能.
在 3.5 版更改: 現(xiàn)在當此函數(shù)被某個不在 sigset 中的信號中斷時將以計算出的 timeout 進行重試并且信號處理程序不會引發(fā)異常(請參閱 PEP 475 了解其理由)。
Examples?
這是一個最小示例程序。 它使用 alarm()
函數(shù)來限制等待打開一個文件所花費的時間;這在文件為無法開啟的串行設備時會很有用處,此情況通常會導致 os.open()
無限期地掛起。 解決辦法是在打開文件之前設置 5 秒鐘的 alarm;如果操作耗時過長,將會發(fā)送 alarm 信號,并且處理程序會引發(fā)一個異常。
import signal, os
def handler(signum, frame):
signame = signal.Signals(signum).name
print(f'Signal handler called with signal {signame} ({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
信號在其標準輸出的接收方提前關(guān)閉時被發(fā)送到你的進程。 這將引發(fā)一個異常例如 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()
Do not set SIGPIPE
's disposition to SIG_DFL
in
order to avoid BrokenPipeError
. Doing that would cause
your program to exit unexpectedly whenever any socket
connection is interrupted while your program is still writing to
it.
Note on Signal Handlers and Exceptions?
If a signal handler raises an exception, the exception will be propagated to
the main thread and may be raised after any bytecode instruction. Most
notably, a KeyboardInterrupt
may appear at any point during execution.
Most Python code, including the standard library, cannot be made robust against
this, and so a KeyboardInterrupt
(or any other exception resulting from
a signal handler) may on rare occasions put the program in an unexpected state.
To illustrate this issue, consider the following code:
class SpamContext:
def __init__(self):
self.lock = threading.Lock()
def __enter__(self):
# If KeyboardInterrupt occurs here, everything is fine
self.lock.acquire()
# If KeyboardInterrupt occcurs here, __exit__ will not be called
...
# KeyboardInterrupt could occur just before the function returns
def __exit__(self, exc_type, exc_val, exc_tb):
...
self.lock.release()
For many programs, especially those that merely want to exit on
KeyboardInterrupt
, this is not a problem, but applications that are
complex or require high reliability should avoid raising exceptions from signal
handlers. They should also avoid catching KeyboardInterrupt
as a means
of gracefully shutting down. Instead, they should install their own
SIGINT
handler. Below is an example of an HTTP server that avoids
KeyboardInterrupt
:
import signal
import socket
from selectors import DefaultSelector, EVENT_READ
from http.server import HTTPServer, SimpleHTTPRequestHandler
interrupt_read, interrupt_write = socket.socketpair()
def handler(signum, frame):
print('Signal handler called with signal', signum)
interrupt_write.send(b'\0')
signal.signal(signal.SIGINT, handler)
def serve_forever(httpd):
sel = DefaultSelector()
sel.register(interrupt_read, EVENT_READ)
sel.register(httpd, EVENT_READ)
while True:
for key, _ in sel.select():
if key.fileobj == interrupt_read:
interrupt_read.recv(1)
return
if key.fileobj == httpd:
httpd.handle_request()
print("Serving on port 8000")
httpd = HTTPServer(('', 8000), SimpleHTTPRequestHandler)
serve_forever(httpd)
print("Shutdown...")