Python使用signal模組實現定時執行

pythontab發表於2015-11-18

在liunx系統中要想每隔一分鐘執行一個命令,最普遍的方法就是crontab了,如果不想使用crontab,經同事指點在程式中可以用定時器實現這種功能,於是就開始摸索了,發現需要一些訊號的知識...

檢視你的linux支援哪些訊號:kill -l 即可

root@server:~# kill -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX
root@server:~#

訊號:程式之間通訊的方式,是一種軟體中斷。一個程式一旦接收到訊號就會打斷原來的程式執行流程來處理訊號。作業系統規定了程式收到訊號以後的預設行為,但是,我們可以透過繫結訊號處理函式來修改程式收到訊號以後的行為,有兩個訊號是不可更改的SIGTOP和SIGKILL。 

傳送訊號一般有兩種原因:

1(被動式)  核心檢測到一個系統事件.例如子程式退出會像父程式傳送SIGCHLD訊號.鍵盤按下control+c會傳送SIGINT訊號

2(主動式)  透過系統呼叫kill來向指定程式傳送訊號

在C語言中有個setitimer函式,函式setitimer可以提供三種定時器,它們相互獨立,任意一個定時完成都將傳送定時訊號到程式,並且自動重新計時。引數which確定了定時器的型別:

ITIMER_REAL       定時真實時間,與alarm型別相同。              SIGALRM

ITIMER_VIRT       定時程式在使用者態下的實際執行時間。            SIGVTALRM

ITIMER_PROF       定時程式在使用者態和核心態下的實際執行時間。      SIGPROF

這三種定時器定時完成時給程式傳送的訊號各不相同,其中ITIMER_REAL類定時器傳送SIGALRM訊號,ITIMER_VIRT類定時器傳送SIGVTALRM訊號,ITIMER_REAL類定時器傳送SIGPROF訊號。

函式alarm本質上設定的是低精確、非過載的ITIMER_REAL類定時器,它只能精確到秒,並且每次設定只能產生一次定時。函式setitimer設定的定時器則不同,它們不但可以計時到微妙(理論上),還能自動迴圈定時。在一個Unix程式中,不能同時使用alarm和ITIMER_REAL類定時器。

SIGINT    終止程式     中斷程式  (control+c)

SIGTERM   終止程式     軟體終止訊號

SIGKILL   終止程式     殺死程式

SIGALRM   鬧鐘訊號

前期的知識也準備的差不多了,該向python的signal進軍了。

定義訊號名

signal包定義了各個訊號名及其對應的整數,比如

import signal
print signal.SIGALRM
print signal.SIGCONT

Python所用的訊號名和Linux一致。你可以透過

$man 7 signal

查詢

預設訊號處理函式

signal包的核心是使用signal.signal()函式來預設(register)訊號處理函式,如下所示:

singnal.signal(signalnum, handler)

signalnum為某個訊號,handler為該訊號的處理函式。我們在訊號基礎裡提到,程式可以無視訊號,可以採取預設操作,還可以自定義操作。當handler為signal.SIG_IGN時,訊號被無視(ignore)。當handler為singal.SIG_DFL,程式採取預設操作(default)。當handler為一個函式名時,程式採取函式中定義的操作。

import signal
# Define signal handler function
def myHandler(signum, frame):
  print('I received: ', signum)
 
# register signal.SIGTSTP's handler 
signal.signal(signal.SIGTSTP, myHandler)
signal.pause()
print('End of Signal Demo')

在主程式中,我們首先使用signal.signal()函式來預設訊號處理函式。然後我們執行signal.pause()來讓該程式暫停以等待訊號,以等待訊號。當訊號SIGUSR1被傳遞給該程式時,程式從暫停中恢復,並根據預設,執行SIGTSTP的訊號處理函式myHandler()。myHandler的兩個引數一個用來識別訊號(signum),另一個用來獲得訊號發生時,程式棧的狀況(stack frame)。這兩個引數都是由signal.singnal()函式來傳遞的。

上面的程式可以儲存在一個檔案中(比如test.py)。我們使用如下方法執行:

$python test.py

以便讓程式執行。當程式執行到signal.pause()的時候,程式暫停並等待訊號。此時,透過按下CTRL+Z向該程式傳送SIGTSTP訊號。我們可以看到,程式執行了myHandle()函式, 隨後返回主程式,繼續執行。(當然,也可以用$ps查詢process ID, 再使用$kill來發出訊號。)

(程式並不一定要使用signal.pause()暫停以等待訊號,它也可以在進行工作中接受訊號,比如將上面的signal.pause()改為一個需要長時間工作的迴圈。)

我們可以根據自己的需要更改myHandler()中的操作,以針對不同的訊號實現個性化的處理。

定時發出SIGALRM訊號

一個有用的函式是signal.alarm(),它被用於在一定時間之後,向程式自身傳送SIGALRM訊號:

import signal
# Define signal handler function
def myHandler(signum, frame):
  print("Now, it's the time")
  exit()
 
# register signal.SIGALRM's handler 
signal.signal(signal.SIGALRM, myHandler)
signal.alarm(5)
while True:
  print('not yet')

我們這裡用了一個無限迴圈以便讓程式持續執行。在signal.alarm()執行5秒之後,程式將向自己發出SIGALRM訊號,隨後,訊號處理函式myHandler開始執行。 

傳送訊號

signal包的核心是設定訊號處理函式。除了signal.alarm()向自身傳送訊號之外,並沒有其他傳送訊號的功能。但在os包中,有類似於linux的kill命令的函式,分別為

os.kill(pid, sid)
os.killpg(pgid, sid)

分別向程式和程式組(見Linux程式關係)傳送訊號。sid為訊號所對應的整數或者singal.SIG*。

實際上signal, pause,kill和alarm都是Linux應用程式設計中常見的C庫函式,在這裡,我們只不過是用Python語言來實現了一下。實際上,Python 的直譯器是使用C語言來編寫的,所以有此相似性也並不意外。此外,在Python 3.4中,signal包被增強,訊號阻塞等功能被加入到該包中。我們暫時不深入到該包中。


相關文章