ローリングコンバットピッチなう!

AIとか仮想化とかペーパークラフトとか

Linux+pythonで周期処理を組む(OS時刻の秒の変わり目に同期させる)

pythonで周期処理

以前、以下のようなエントリーを書きました。

rc30-popo.hatenablog.com

pythonでも同様に定周期で何かを実行したい場合があります。
time.sleep()では毎回の処理の時間分だけどんどんタイミングがずれていくので定期的な割り込みを利用します。

ざっくりとは以下の様なイメージ。

import signal,time


# インターバルタイマーハンドラー
def intervalHandler(signum,frame):
    """定期的に実行したい処理を書く"""

# インターバル起床するハンドラを設定(SIGALRMハンドラー)
signal.signal(signal.SIGALRM,intervalHandler)

# インターバルタイマー設定
# 最初の開始タイミング1秒後、以後1秒間隔
signal.setitimer(signal.ITIMER_REAL, 1, 1)


try:
    while True:
# インターバルタイマー待ちの間はずっとsleep
# 時間は取り敢えずなんでも良いが電力消費抑えるなら長めで
# 3600秒とかでも良い
        time.sleep(600) 

except KeyboardInterrupt:
    print('\nCTRL-C pressed!!')
    sys.exit()

これでほぼ正確に1秒同期してintervalHandler()内の処理が走ります。

ただ、単に1秒間隔で何か実行するならこれで良いのですが、例えば時計表示の様なものを作りたい場合、OSの時刻の秒の切り替わりタイミングになるべく同期させてインターバルを開始させたくなります。
色々調べたのですが、datetimeを使って秒の変わり目をポーリングし、タイミングを取ってsignal.setitimer()を実行するしか無さそうです。

import time,datetime,signal

# インターバルタイマーハンドラー
def intervalHandler(signum,frame):
    """定期的に実行したい処理を書く"""

# インターバル起床するハンドラを設定(SIGALRMハンドラー)
signal.signal(signal.SIGALRM,intervalHandler)

# 秒の変わり目をポーリング
while True:
    dt_now = datetime.datetime.now()
    if dt_now.microsecond < 10000: # 秒の変わり目から10ms未満ならOK
        print('microsec = %d' % dt_now.microsecond)
        break

# インターバルタイマー設定
# 最初の開始タイミング1秒後、以後1秒間隔
signal.setitimer(signal.ITIMER_REAL, 1, 1)


try:
    while True:
# インターバルタイマー待ちの間はずっとsleep
# 時間は取り敢えずなんでも良いが電力消費抑えるなら長めで
# 3600秒とかでも良い
        time.sleep(600) 

except KeyboardInterrupt:
    print('\nCTRL-C pressed!!')
    sys.exit()

datetime.datetime.now()をポーリング、microsecondがある値より小さけばポーリング終了。
このある値はポーリングの誤差(スケジューラーによる他のプロセスやカーネル処理の割り込みやポーリング自身の処理負荷)を考慮して、ある程度小さい値にします。
上記例では秒の変わり目から10ms以内ならOKにしています。

Raspberry Pi 2 MODEL B+をクロック600Mhz固定で試した限りでは、1回目のポーリングでたまたまこの条件に合致した場合を除き、秒の変わり目からだいたい20μsec以内くらいでインターバル処理は開始出来る様です。10msではなく、少し余裕見ても100μsecくらいまでソースコード上は詰めても大丈夫そうです。

この処理は以下のエントリーで書いた、Raspberry Piで気圧センサー、温度センサーの値を読み出し、時刻と合わせて毎秒表示する処理で使って見ました。
rc30-popo.hatenablog.com

最新ソースはこちら。
github.com