ループを1秒に1回正確に実行する


33

このループを実行して、毎秒いくつかのことを確認して印刷します。ただし、計算には数百ミリ秒かかることがあるため、印刷時間は1秒スキップすることがあります。

毎秒印刷することが保証されているようなループを書く方法はありますか?(もちろん、ループ内の計算にかかる時間は1秒未満です:))

while true; do
  TIME=$(date +%H:%M:%S)
  # some calculations which take a few hundred milliseconds
  FOO=...
  BAR=...
  printf '%s  %s  %s\n' $TIME $FOO $BAR
  sleep 1
done


26
「と注意正確にあなたが(通常は)それはフィットを見て、あなたのコードをスケジュールします先制マルチタスクカーネルの上にユーザ空間で実行されているため、ほとんどの場合、文字通り不可能です1秒に1回は、」(あなたがスリープ直後にコントロールを取り戻すない可能性があること終了など)。sched(7)API を呼び出すCコード(POSIX:参照<sched.h>およびそこからリンクされたページ)を書いているのでない限り、基本的にこの形式のリアルタイムの保証はできません。
ケビン

@Kevinが言ったことをバックアップするために、sleep()を使用してあらゆる種類の正確なタイミングを試行しようとすると失敗する運命にあり、少なくとも1秒のスリープのみを保証します。正確なタイミングが本当に必要な場合は、システムクロック(CLOCK_MONOTONICを参照)を見て、最後のイベントからの時間+ 1に基づいて物事をトリガーする必要があり、実行に1秒以上かかることでつまずかないようにしてください。何らかの操作の次回の計算など
ジョンU

これをここに残しますfalsehoodsabouttime.com
アロマルバレス

正確に1秒に1回= VCXOを使用します。ソフトウェアのみのソリューションでは、「十分」に到達できますが、正確ではありません。
イアンマクドナルド

回答:


65

元のコードに少し近づけるために、私がやることは:

while true; do
  sleep 1 &
  ...your stuff here...
  wait # for sleep
done

これにより、セマンティクスが少し変更されます。1秒もかからなかった場合は、1秒が経過するのを待つだけです。ただし、なんらかの理由で1秒よりも時間がかかる場合、終わりのないサブプロセスがさらに生成され続けることはありません。

したがって、バックグラウンドではなく並行して実行されることはないため、変数も期待どおりに機能します。

追加のバックグラウンドタスクも開始する場合waitは、sleep特にプロセスを待機するように命令を変更する必要があることに注意してください。

さらに正確にする必要がある場合は、システムクロックに同期して、1秒ではなくmsスリープする必要があります。


システムクロックに同期する方法 まったくわからない、愚かな試み:

デフォルト:

while sleep 1
do
    date +%N
done

出力:003511461 010510925 016081282 021643477 028504349 03 ...(キープ成長)

同期済み:

 while sleep 0.$((1999999999 - 1$(date +%N)))
 do
     date +%N
 done

出力:002648691 001098397 002514348 001293023 001679137 00 ...(同じまま)


9
このスリープ/待機トリックは本当に賢いです!
philfr

すべての実装がsleep小数秒を処理するかどうか疑問に思っていますか?
jcaron

1
@jcaronはすべてではありません。しかし、これはgnuスリープとbusyboxスリープで機能するため、エキゾチックではありません。おそらくsleep 0.9 || sleep 1、無効なパラメーターがスリープが失敗する唯一の理由であるため、単純なフォールバックを行うことができます。
frostschutz

@frostschutz私はナイーブな実装sleep 0.9として解釈さsleep 0れることを期待しています(それが何をatoiするかを考えると)。それが実際にエラーになるかどうかはわかりません。
jcaron

1
この質問に多くの関心が寄せられたことを嬉しく思います。あなたの提案と答えはとても良いです。1秒以内に保持されるだけでなく、可能な限り1秒全体に密着します。印象的!(追記!補足として、GNU Coreutilsをインストールしgdate、macOS で使用してdate +%N作業を行う必要があります。)
forrin

30

ループをスクリプト/ワンライナーに再構築できる場合、これを行う最も簡単な方法は、with watchとそのpreciseオプションです。

効果は次のwatch -n 1 sleep 0.5ように表示されます-秒のカウントアップが表示されますが、1秒以上スキップすることもあります。として実行すると、watch -n 1 -p sleep 0.5毎秒2回、毎秒出力され、スキップは発生しません。


11

バックグラウンドジョブとして実行されるサブシェルで操作を実行すると、sleep

while true; do
  (
    TIME=$(date +%T)
    # some calculations which take a few hundred milliseconds
    FOO=...
    BAR=...
    printf '%s  %s  %s\n' "$TIME" "$FOO" "$BAR"
  ) &
  sleep 1
done

1秒から「盗まれた」唯一の時間は、サブシェルを起動するのにかかる時間です。したがって、最終的には1秒スキップしますが、できれば元のコードよりも頻繁ではありません。

サブシェルのコードが最終的に1秒以上使用されると、ループはバックグラウンドジョブを蓄積し始め、最終的にリソースを使い果たします。


9

別の代替手段(たとえば、watch -pMaelstromが示唆しているように使用できない場合)は、このために設計されたsleepenh[ manpage ]です。

例:

#!/bin/sh

t=$(sleepenh 0)
while true; do
        date +'sec=%s ns=%N'
        sleep 0.2
        t=$(sleepenh $t 1)
done

sleep 0.2そこには、約200ミリ秒を消費する時間のかかるタスクをシミュレートすることに注意してください。それにもかかわらず、ナノ秒の出力は安定したままです(まあ、非リアルタイムOS標準による)— 1秒に1回発生します:

sec=1533663406 ns=840039402
sec=1533663407 ns=840105387
sec=1533663408 ns=840380678
sec=1533663409 ns=840175397
sec=1533663410 ns=840132883
sec=1533663411 ns=840263150
sec=1533663412 ns=840246082
sec=1533663413 ns=840259567
sec=1533663414 ns=840066687

それは1ミリ秒未満の違いで、トレンドはありません。それはかなりいいです。システムに負荷がかかっている場合でも、少なくとも10ミリ秒のバウンスが予想されますが、時間の経過に伴うドリフトはありません。すなわち、あなたは秒を失うことはありません。


7

zsh

n=0
typeset -F SECONDS=0
while true; do
  date '+%FT%T.%2N%z'
  ((++n > SECONDS)) && sleep $((n - SECONDS))
done

あなたの睡眠は、浮動小数点秒をサポートしていない場合は、使用することができるzshのをzselect(後の代わりにzmodload zsh/zselect):

zmodload zsh/zselect
n=0
typeset -F SECONDS=0
while true; do
  date '+%FZ%T.%2N%z'
  ((++n > SECONDS)) && zselect -t $((((n - SECONDS) * 100) | 0))
done

ループ内のコマンドの実行に1秒未満しかかからない限り、これらはドリフトしないはずです。


0

すべてのヘルパー(usleep、GNUsleep、sleepenh、...)が利用できないPOSIXシェルスクリプトに対してもまったく同じ要件がありました。

参照:https : //stackoverflow.com/a/54494216

#!/bin/sh

get_up()
{
        read -r UP REST </proc/uptime
        export UP=${UP%.*}${UP#*.}
}

wait_till_1sec_is_full()
{
    while true; do
        get_up
        test $((UP-START)) -ge 100 && break
    done
}

while true; do
    get_up; START=$UP

    your_code

    wait_till_1sec_is_full
done
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.