為什么不建議使用 Time.Sleep 實現定時功能?
有時候,我們想實現一個非常簡單的定時功能,例如讓一個程序每天早上8點調用某個函數。但我們又不想安裝任何第三方庫,也不會使用 crontab 或者任務計劃功能,就想使用純 Python 來實現。
可能有同學會這樣寫代碼:
- import time
- import datetime
- def run():
- print('我是需要被每天調用的函數')
- def schedule():
- target_time = datetime.time(8, 0, 0)
- today = datetime.date.today()
- target_date = today + datetime.timedelta(days=1)
- target_datetime = datetime.datetime.combine(target_date, target_time)
- now = datetime.datetime.now()
- delta = (target_datetime - now).total_seconds()
- time.sleep(delta)
- run()
- while True:
- time.sleep(24 * 3600)
- run()
- if __name__ == '__main__':
- schedule()
這段程序,首先計算出現在距離明天早上8點相差的秒數。睡這么多秒以后,第一次運行目標函數。然后進入一個死循環,每隔86400秒,程序調用一次 run 函數。
這個程序初看起來,似乎沒有什么問題。但如果你每天觀察它的運行時間,你會發現隨著時間的推移,時間會越來越不準確。
這是因為,run 函數不是一瞬間就運行完成的。它運行也會消耗時間。假設程序第一次運行 run 函數的時候,確實剛剛好是8:00,run 函數運行了2秒。那么,程序睡眠86400秒以后,時間實際上是8:00:02.從第二天開始,每天晚2秒鐘。一個月就會晚一分鐘。
但實際上,我們如果付出一點點微不足道的代價,我們就可以防止這種誤差的發生,并且程序代碼會變得更簡單:
- import time
- import datetime
- def run():
- print('我是需要被每天調用的函數')
- def schedule():
- last_run = None
- while True:
- now = datetime.datetime.now()
- if now.strftime('%H:%M') == '08:00' and last_run != now.date():
- run()
- last_run = now.date()
- time.sleep(1)
- if __name__ == '__main__':
- schedule()
程序在一個死循環中,每秒做一次檢查,如果當前的時分正好是08:00,并且上一次運行不是今天,那么就調用 run 函數,并把上一次運行的時間設置為今天。否則,就睡眠1秒鐘。
這樣做,相當于每秒都會校對時間,從而避免了長時間運行導致的時間誤差。雖然看起來這個死循環會非常消耗 CPU,但只要你算一下,實際上它只不過每天循環86400次而已。這個次數并不多。
但無論如何,專業的事情應該交由專業的工具來做。time.sleep用來設置周期性的時間間隔可以,但它實際上不適合用來做定時任務。
因為一個支持定時任務的庫,例如 Python 的schedule或者APScheduler,他們在確保定時時間準確上,做了很多工作。還有一些庫甚至用到了時間輪這樣的數據結構來確保時間的準確性。這不是我們簡單用兩三行 Python 代碼就能完成的。
總結
如果能用 crontab 或者任務計劃,那么這是最優選擇。其次,使用 Python 專用的定時模塊。最次,才是使用 time.sleep 來實現。如果不得不用 time.sleep,那么應該盡量縮短檢查的間隔,避免長時間睡眠。
本文轉載自微信公眾號「未聞Code」,可以通過以下二維碼關注。轉載本文請聯系未聞Code公眾號。