鳥の巣箱

ネトゲしたり、機械いじったり、ソフト書いたり、山篭ったり、ギャンブルしたりする人

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