所謂定時器,是指間隔特定時間執行特定任務的機制,幾乎所有的編程語言,都有定時器的實作,比如,Java有util.Timer和util.TimerTask,JavaScript有setInterval和setTimeout,可以實作非常復雜的定時任務處理,然而,牛叉到無所不能的Python,卻沒有一個像樣的定時器,實在令人難以理解,
剛入門的同學一定會說:不是有個time.sleep嗎?定好鬧鐘睡大覺,鬧鐘一響,起來干活,這不就是一個定時器嗎?沒錯,time.sleep具備定時器的基本要素,但若作為定時器使用,則有兩個致命的缺陷:一是阻塞主執行緒,睡覺的時候不能做任何事情;二是醒來以后需要主執行緒執行定時任務——即便使用執行緒技術,也得先由主執行緒來創建子執行緒,
說到這里,熟悉執行緒模塊threading的同學也許會說:threading.Timer就是以執行緒方式運行的呀,既不會阻塞主執行緒,執行定時任務也無需主執行緒干預,這不就是一個完美的定時器嗎?
我們先來看看threading.Timer是如何作業的,下面這段代碼演示了threading.Timer的基本用法:啟動定時器2秒鐘后以執行緒方式呼叫函式do_something,在定時器等待的2秒鐘內,以及do_something運行期間,主執行緒仍然可以做其他作業——此處是從鍵盤讀取輸入,借以阻塞主執行緒,以便觀察定時器的作業情況,
import time
import threading
def do_something(name, gender='male'):
print(time.time(), '定時時間到,執行特定任務' )
print('name:%s, gender:%s'%(name, gender))
timer = threading.Timer(2, do_something, args=('Alice',), kwargs={'gender':'female'})
timer.start()
print(time.time(), '定時開始時間')
input('按回車鍵結束\n') # 此處阻塞住行程
正如我們所期待的那樣,定時器啟動2秒鐘后,函式do_something被呼叫,這期間可以隨時敲擊回車鍵結束程式,這段代碼的運行結果如下,
1627438957.4297626 定時開始時間
按回車鍵結束
1627438959.4299397 定時時間到,執行特定任務
name:Alice, gender:female
從使用效果看,threading.Timer稱得上是一款簡潔易用的定時器,不過,threading.Timer存在明顯的短板,那就是不支持連續的定時任務,比如,每隔2秒鐘呼叫一次do_something函式,如果一定要用threading.Timer實作連續定時,只能用類似嵌套的變通方法,在do_something函式中再次啟動定時器,
import time
import threading
def do_something(name, gender='male'):
global timer
timer = threading.Timer(2, do_something, args=(name,), kwargs={'gender':gender})
timer.start()
print(time.time(), '定時時間到,執行特定任務' )
print('name:%s, gender:%s'%(name, gender))
time.sleep(5)
print(time.time(), '完成特定任務' )
timer = threading.Timer(2, do_something, args=('Alice',), kwargs={'gender':'female'})
timer.start()
input('按回車鍵結束\n') # 此處阻塞住行程
這段代碼重新定義了do_something函式,在函式開始位置啟動下一次的定時任務,之所以放在開始位置,是為了保證兩次定時之間的時間間隔盡可能精確,饒是如此,下面的運行結果顯示,兩次定時之間的時間間隔比設計的2秒鐘多了大約10毫秒,且誤差是連續累計的,重復執行100次,誤差將會超過1秒鐘,
按回車鍵結束
1627440628.683803 定時時間到,執行特定任務
name:Alice, gender:female
1627440630.6929214 定時時間到,執行特定任務
name:Alice, gender:female
1627440632.707388 定時時間到,執行特定任務
name:Alice, gender:female
1627440633.6890671 完成特定任務
1627440634.722474 定時時間到,執行特定任務
name:Alice, gender:female
1627440635.7092102 完成特定任務
1627440636.7277966 定時時間到,執行特定任務
name:Alice, gender:female
針對連續的定時任務,threading.Timer的表現還算差強人意,只是這種嵌套的寫法完全顛覆了代碼美學,對于像我這樣有代碼潔癖的程式員來說,是無法容忍和不可接受的,在我看來,一個完美的定時器應該滿足以下5個條件,具備下圖所示的結構,
- 不阻塞主執行緒
- 同時支持單次定時和連續定時
- 以執行緒或行程方式執行定時任務
- 定時任務的執行緒或行程的創建、運行,不影響定時精度
- 足夠精確的定時精度,且誤差不會累計

既然Python沒有提供一個像樣的定時器,那就自己寫一個吧,下面這個定時器,滿足上面提到的5個條件,最短時間間隔可以低至10毫秒,且誤差不會累計,雖然還不夠完美,但無論結構還是精度,都還說得過去,
import time
import threading
class PyTimer:
"""定時器類"""
def __init__(self, func, *args, **kwargs):
"""建構式"""
self.func = func
self.args = args
self.kwargs = kwargs
self.running = False
def _run_func(self):
"""運行定時事件函式"""
th = threading.Thread(target=self.func, args=self.args, kwargs=self.kwargs)
th.setDaemon(True)
th.start()
def _start(self, interval, once):
"""啟動定時器的執行緒函式"""
if interval < 0.010:
interval = 0.010
if interval < 0.050:
dt = interval/10
else:
dt = 0.005
if once:
deadline = time.time() + interval
while time.time() < deadline:
time.sleep(dt)
# 定時時間到,呼叫定時事件函式
self._run_func()
else:
self.running = True
deadline = time.time() + interval
while self.running:
while time.time() < deadline:
time.sleep(dt)
# 更新下一次定時時間
deadline += interval
# 定時時間到,呼叫定時事件函式
if self.running:
self._run_func()
def start(self, interval, once=False):
"""啟動定時器
interval - 定時間隔,浮點型,以秒為單位,最高精度10毫秒
once - 是否僅啟動一次,默認是連續的
"""
th = threading.Thread(target=self._start, args=(interval, once))
th.setDaemon(True)
th.start()
def stop(self):
"""停止定時器"""
self.running = False
定時器類PyTimer實體化時,需要傳入定時任務函式,如果定時任務函式有引數,也可以按照位置引數、關鍵字引數的順序一并提供,PyTimer定時器提供start和stop兩個方法,用于啟動和停止定時器,其中stop方法不需要引數,start則需要一個以秒為單位的定時間隔引數,start還有一個布爾型的默認引數once,可以設定是否單次定時,once引數的默認值為False,即默認連續定時;如果需要單次定時,只需要將once置為true即可,
def do_something(name, gender='male'):
print(time.time(), '定時時間到,執行特定任務' )
print('name:%s, gender:%s'%(name, gender))
time.sleep(5)
print(time.time(), '完成特定任務' )
timer = PyTimer(do_something, 'Alice', gender='female')
timer.start(0.5, once=False)
input('按回車鍵結束\n') # 此處阻塞住行程
timer.stop()
上面是使用PyTimer定時器以0.5秒鐘的間隔連續呼叫函式do_something的例子,這段代碼的運行結果如下,
按回車鍵結束
1627450313.425347 定時時間到,執行特定任務
name:Alice, gender:female
1627450313.9226055 定時時間到,執行特定任務
name:Alice, gender:female
1627450314.421761 定時時間到,執行特定任務
name:Alice, gender:female
1627450314.9243422 定時時間到,執行特定任務
name:Alice, gender:female
1627450315.422722 定時時間到,執行特定任務
name:Alice, gender:female
1627450315.9200313 定時時間到,執行特定任務
name:Alice, gender:female
1627450316.4204514 定時時間到,執行特定任務
name:Alice, gender:female
1627450316.9215539 定時時間到,執行特定任務
name:Alice, gender:female
1627450317.4228196 定時時間到,執行特定任務
name:Alice, gender:female
1627450317.9245899 定時時間到,執行特定任務
name:Alice, gender:female
1627450318.42355 定時時間到,執行特定任務
name:Alice, gender:female
1627450318.4393418 完成特定任務
1627450318.9251466 定時時間到,執行特定任務
name:Alice, gender:female
1627450318.9395308 完成特定任務
1627450319.4242043 完成特定任務
1627450319.4242043 定時時間到,執行特定任務
name:Alice, gender:female
1627450319.9253905 定時時間到,執行特定任務
name:Alice, gender:female
1627450319.9411068 完成特定任務
1627450320.425871 完成特定任務
1627450320.425871 定時時間到,執行特定任務
name:Alice, gender:female
雖然每個定時任務需要運行5秒鐘,但每隔0.5秒都會準時啟動一個新的執行緒運行定時任務,從記錄可以看出,盡管每次定時任務的啟動時間有幾個毫秒的誤差,但誤差不會累計,重復執行的時間間隔均值始終穩定在0.5秒,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/290890.html
標籤:python
上一篇:整個大活,采集8個代理IP站點,為Python代理池鋪路,爬蟲120例之第15例
下一篇:java8特性匯總
