it-swarm.com.de

Führen Sie eine Schleife genau einmal pro Sekunde aus

Ich führe diese Schleife aus, um einige Dinge jede Sekunde zu überprüfen und zu drucken. Da die Berechnungen jedoch einige hundert Millisekunden dauern, überspringt die gedruckte Zeit manchmal eine Sekunde.

Gibt es eine Möglichkeit, eine solche Schleife zu schreiben, bei der ich garantiert jede Sekunde einen Ausdruck bekomme? (Vorausgesetzt natürlich, dass die Berechnungen in der Schleife weniger als eine Sekunde dauern :))

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
33
forthrin

Um ein bisschen näher am Originalcode zu bleiben, mache ich Folgendes:

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

Dies ändert die Semantik ein wenig: Wenn Ihr Material weniger als eine Sekunde gedauert hat, wartet es einfach, bis die volle Sekunde verstrichen ist. Wenn Ihr Material jedoch aus irgendeinem Grund länger als eine Sekunde dauert, werden nicht immer mehr Unterprozesse erzeugt, ohne dass dies zu Ende geht.

Ihre Inhalte werden also nie parallel und nicht im Hintergrund ausgeführt, sodass Variablen auch wie erwartet funktionieren.

Beachten Sie, dass Sie, wenn Sie auch zusätzliche Hintergrundaufgaben starten, die Anweisung wait ändern müssen, um nur speziell auf den Prozess sleep zu warten.

Wenn Sie es noch genauer benötigen, müssen Sie es wahrscheinlich nur mit der Systemuhr synchronisieren und ms statt voller Sekunden in den Ruhezustand versetzen.


Wie synchronisiere ich mit der Systemuhr? Keine Ahnung wirklich, dummer Versuch:

Standard:

while sleep 1
do
    date +%N
done

Ausgabe: 003511461 010510925 016081282 021643477 028504349 03 ... (wächst weiter)

Synchronisiert:

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

Ausgabe: 002648691 001098397 002514348 001293023 001679137 00 ... (bleibt gleich)

65
frostschutz

Wenn Sie Ihre Schleife in ein Skript/einen Oneliner umstrukturieren können, ist dies am einfachsten mit watch und der Option precise.

Sie können den Effekt mit watch -n 1 sleep 0.5 Sehen - es werden Sekunden hochgezählt, aber gelegentlich wird eine Sekunde übersprungen. Wenn Sie es als watch -n 1 -p sleep 0.5 Ausführen, wird jede Sekunde zweimal pro Sekunde ausgegeben, und Sie sehen keine Sprünge.

30
Maelstrom

Das Ausführen der Operationen in einer Subshell, die als Hintergrundjob ausgeführt wird, würde dazu führen, dass sie das sleep nicht so stark beeinträchtigen.

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

Die einzige Zeit, die von einer Sekunde "gestohlen" wurde, war die Zeit, die zum Starten der Subshell benötigt wurde, sodass schließlich eine Sekunde übersprungen wurde, aber hoffentlich weniger häufig als der ursprüngliche Code.

Wenn der Code in der Subshell am Ende mehr als eine Sekunde verwendet, sammelt die Schleife Hintergrundjobs und hat schließlich keine Ressourcen mehr.

11
Kusalananda

Eine andere Alternative (wenn Sie nicht verwenden können, z. B. watch -p wie Maelstrom vorschlägt) ist sleepenh [ manpage ], was dafür ausgelegt ist.

Beispiel:

#!/bin/sh

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

Beachten Sie das sleep 0.2 dort simulieren die Simulatoren eine zeitaufwändige Aufgabe und essen etwa 200 ms. Trotzdem bleibt die Nanosekunden-Ausgabe stabil (im Vergleich zu Nicht-Echtzeit-Betriebssystemstandards) - dies geschieht einmal pro Sekunde:

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

Das ist weniger als 1 ms anders und kein Trend. Das ist ziemlich gut; Sie sollten mit Sprüngen von mindestens 10 ms rechnen, wenn das System belastet wird - aber immer noch keine Abweichung im Laufe der Zeit. Das heißt, Sie werden keine Sekunde verlieren.

9
derobert

Mit zsh:

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

Wenn Ihr Schlaf keine Gleitkomma-Sekunden unterstützt, können Sie stattdessen zshs zselect verwenden (nach einem 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

Diese sollten nicht driften, solange die Ausführung der Befehle in der Schleife weniger als eine Sekunde dauert.

7

Ich hatte genau die gleiche Anforderung für ein POSIX-Shell-Skript, bei dem nicht alle Helfer (usleep, GNUsleep, sleepenh, ...) verfügbar sind.

siehe: 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
0
Bastian Bittorf