避免在 bash 中忙等待,无需 sleep 命令

避免在 bash 中忙等待,无需 sleep 命令

我知道我可以通过执行以下操作来等待 bash 中的条件变为真:

while true; do
  test_condition && break
  sleep 1
done

但它在每次迭代(睡眠)时创建 1 个子进程。我可以通过这样做来避免它们:

while true; do
  test_condition && break
done

但它使用大量CPU(忙等待)。为了避免子流程和忙等待,我想出了下面的解决方案,但我觉得它很难看:

my_tmp_dir=$(mktemp -d --tmpdir=/tmp)    # Create a unique tmp dir for the fifo.
mkfifo $my_tmp_dir/fifo                  # Create an empty fifo for sleep by read.
exec 3<> $my_tmp_dir/fifo                # Open the fifo for reading and writing.

while true; do
  test_condition && break
  read -t 1 -u 3 var                     # Same as sleep 1, but without sub-process.
done

exec 3<&-                                # Closing the fifo.
rm $my_tmp_dir/fifo; rmdir $my_tmp_dir   # Cleanup, could be done in a trap.

注意:在一般情况下,我不能简单地read -t 1 var在没有 fifo 的情况下使用,因为它会消耗 stdin,并且如果 stdin 不是终端或管道,则它将不起作用。

我可以用更优雅的方式避免子流程和忙碌等待吗?

答案1

bash在较新版本(至少 v2)中,内置函数可以在运行时加载(通过)enable -f filename commandname。许多这样的可加载内置函数也随 bash 源一起分发,并且sleep位于其中。当然,可用性可能因操作系统(甚至机器不同)而异。例如,在 openSUSE 上,这些内置函数通过包分发bash-loadables

答案2

在内部循环中创建大量子进程是一件坏事。sleep每秒创建一个进程就可以了。没有什么问题

while ! test_condition; do
  sleep 1
done

如果你确实想避免外部进程,则不需要保持fifo打开。

my_tmpdir=$(mktemp -d)
trap 'rm -rf "$my_tmpdir"' 0
mkfifo "$my_tmpdir/f"

while ! test_condition; do
  read -t 1 <>"$my_tmpdir/f"
done

答案3

我最近有需要这样做。我想出了以下函数,可以让 bash 永远休眠而不调用任何外部程序:

snore()
{
    local IFS
    [[ -n "${_snore_fd:-}" ]] || { exec {_snore_fd}<> <(:); } 2>/dev/null ||
    {
        # workaround for MacOS and similar systems
        local fifo
        fifo=$(mktemp -u)
        mkfifo -m 700 "$fifo"
        exec {_snore_fd}<>"$fifo"
        rm "$fifo"
    }
    read ${1:+-t "$1"} -u $_snore_fd || :
}

注意:我之前发布了一个每次都会打开和关闭文件描述符的版本,但我发现在某些系统上每秒执行数百次最终会锁定。因此,新的解决方案在函数调用之间保留文件描述符。无论如何,Bash 都会在退出时清理它。

这可以像 /bin/sleep 一样调用,并且它将在请求的时间内休眠。不带参数调用,它将永远挂起。

snore 0.1  # sleeps for 0.1 seconds
snore 10   # sleeps for 10 seconds
snore      # sleeps forever

我的博客上有一篇包含过多细节的文章

答案4

Inksh93mksh,sleep是一个内置 shell,因此另一种选择可能是使用这些 shell 而不是bash

zsh还有一个zselect内置函数(加载了zmodload zsh/zselect),可以使用 休眠给定的百分之几秒zselect -t <n>

相关内容