Python: 可中斷的 Sleep

·

1 min read

在 Python 裡面,有個很好用的函數,叫做 time.sleep ,他可以讓程式暫停幾秒,不過要讓程式更好,需要再對 “Sleep” 更了解一些。

用途

就我來說,會用到的情況不外乎這幾種:

  1. 需要 polling 的等待函數
  2. 定期執行程式的 cronjob
  3. 暫時性的等待資源的 work around。
# 只是一個 Polling 陽春的範例  
def wait_for_data():  
    while True:  
        time.sleep(1)  
        if poll_ok():  
            return

最後一種姑且不談,畢竟這上產品線時是通常要修掉的,前面兩個使用時,就需要考慮到一個問題,如何提前“中斷”,例如在 Golang 裡面,就可以用 context 去中斷。Cronjob 假如是用 supervisord 進行管理,那在吃 Signal 時也會要好好的處理:中斷 time.sleep 並且做 Graceful Shutdown。

但是,time.sleep 的介面,並沒有直接提供提前中斷的功能。

Cronjob: Main Thread + Signal Handler

假如,使用情境是 Cronjob,只有 main thread,然後定期跑一個函數。

def main():  
    while True:  
        time.sleep(??)  
        do_job()

然後,我們希望在送 SIGTERM 時,可以中斷程式結束,當然,在 do_job 時收到 SIGTERM的話,處理方式也視情況,看是直接中斷或者等這一輪處理完再結束。這裏先當要跑完完整的一輪。

time.sleep 雖然在的介面上並沒有提供任何中斷方式。但是根據官方文件

Changed in version 3.5: The function now sleeps at least secs even if the sleep is interrupted by a signal, except if the signal handler raises an exception.

他可以被 signal 中斷:在 signal 裡面扔出一個特定的 Exception,然後讓 sleep 外面抓這個 Exception。

Cronjob: threading.Event

扔 Exception 仍然不是一個好的方案,他某些程度上來說仍然算髒,所以有第二個方案:使用 threading.Event

當 event 物件被 set 時,wait 會中斷,並且離開迴圈。這樣會比起【期待 time.Sleep 會扔出 signal 扔的 Exception】來的乾淨。

參考