Pythonで一定間隔で処理をさせる

Programing,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()の内容が条件によって分岐したりする場合、処理時間が必ず一定ではない場合もある。
そうするともはや一定周期で動作しているとは言えないよね。っていう話です。

システムコールとシグナルを使う

で、これを解決する方法としてシステムコールとシグナルを使います。

具体的なソースは以下のような感じ。

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などを使うことで同じ事が可能です。

Programing,Python

Posted by tsubakurame