每秒精确运行一次循环

每秒精确运行一次循环

我正在运行这个循环来每秒检查并打印一些内容。然而,由于计算可能需要几百毫秒,因此打印时间有时会跳过一秒。

有没有办法编写这样一个循环,保证每秒都能得到打印输出? (当然,前提是循环中的计算时间少于一秒:))

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

答案1

为了更接近原始代码,我所做的是:

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

这稍微改变了语义:如果你的东西花了不到一秒,它只会等待整秒过去。然而,如果你的东西由于某种原因花费了超过一秒的时间,它不会继续产生更多的子进程,永远不会结束。

所以你的东西永远不会并行运行,也不会在后台运行,因此变量也按预期工作。

请注意,如果您确实还启动了其他后台任务,则必须将指令更改wait为仅专门等待该sleep进程。

如果您需要它更加准确,您可能只需将其同步到系统时钟并睡眠毫秒而不是整秒。


如何同步到系统时钟?真的不知道,愚蠢的尝试:

默认:

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...(保持不变)

答案2

如果您可以将循环重组为脚本/oneliner,那么最简单的方法就是使用watch及其precise选项。

您可以使用以下命令查看效果watch -n 1 sleep 0.5- 它将显示秒数计数,但偶尔会跳过一秒。运行它watch -n 1 -p sleep 0.5会每秒输出两次,每秒,你不会看到任何跳过。

答案3

在作为后台作业运行的子 shell 中运行操作将使它们不会对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

从一秒中“窃取”的唯一时间是启动子 shell 所花费的时间,因此它最终会跳过一秒,但希望比原始代码少。

如果子 shell 中的代码最终使用更多的不到一秒钟,循环就会开始积累后台作业并最终耗尽资源。

答案4

zsh

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

如果您的睡眠不支持浮点秒,您可以使用zsh'szselect代替(在 后面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

只要循环中的命令运行时间少于一秒,这些就不应该漂移。

相关内容