Pythonで一定間隔で処理をさせる
pythonで、例えばある関数を1秒間隔で実行したい時、初心者が一番最初に思いつく方法として
def task(): # 何らかの処理 def main(): while True: task() time.sleep(1)
といった書き方がある。
この書き方でも”およそ1秒毎”には処理してくれる。
だが、task()の処理時間などにこの間隔は大きく依存してくる。
time.sleep(1)はOSのスケジューラーの精度にもよるけど、ほぼ1秒sleepしてくれるがtask()の実行時間などが考慮されてないわけで。
仮にtask()の実行時間に0.1秒かかるのであれば1.1秒の周期で実行されていることになる。
また、task()の内容が条件によって分岐したりする場合、処理時間が必ず一定ではない場合もある。
そうするともはや一定周期で動作しているとは言えないよね。っていう話です。
- 1. システムコールとシグナルを使う
- 2. スレッドで一定間隔の処理を行いたい
システムコールとシグナルを使う
で、これを解決する方法としてシステムコールとシグナルを使います。
具体的なソースは以下のような感じ。
import signal def task(): # 何らかの処理 def main(): signal.signal(signal.SIGALRM, task) signal.setitimer(signal.ITIMER_REAL, 0.1, 1) while True: time.sleep(1)
signal.signal(signal.SIGALRM, task)
これは「signal.SIGALRMが発生したときにtaskを実行してくれ」っていう意味。
signal.setitimer(signal.ITIMER_REAL, 1, 0.1)
で、これが「0.1秒後から1秒間隔でインターバルタイマーを実行してくれ」という意味。
このインターバルタイマーは1秒ごとにsignal.SIGALRMを送ります。
これで1秒ごとの処理が可能になりました。
精度はかなりいいほうです。
スレッドで一定間隔の処理を行いたい
これをスレッドに拡張した場合はどうすればいいのでしょうか。
こういう書き方ができます。
import signal import threading import sys import time def int_timer(signum, stack): event.set() def thread_task(event): while True: event.wait() event.clear() print('1') # 何らかの処理 def main(): global event event = threading.Event() signal.signal(signal.SIGALRM, int_timer) signal.setitimer(signal.ITIMER_REAL, 0.1, 1) thread = threading.Thread(target=thread_task, args=(event,)) thread.start() while True: time.sleep(1) if __name__ == "__main__": sys.exit(main())
別スレッドで実行されるthread_taskはevent.wait()によって、event.set()がされるまでブロックされます。
event.set()はインターバルタイマーによって呼び出されるint_timerの中で実行されます。
これは1秒に1回実行されるので、event.set()を待っているスレッド側も1秒に1回しかevent.wait()の先には進みません。
event.wait()の後でevent.clear()をするのは、setされたままの状態をリセットするため。
clearをしないと、ループでまたevent.wait()に戻ったときにそのままスルーして処理が実行されてしまいます。
eventを使ってスレッドの動作タイミングを制御する方法のメリットは、複数のスレッドを別周期で動作させることも簡単にできる点です。
また、スレッド間の同期を取る場合でもeventは使われます。
multiprocessを使う場合はpipeなどを使うことで同じ事が可能です。
ディスカッション
コメント一覧
まだ、コメントがありません