避免在 bash 中忙等待,没有 sleep 命令

jfg*_*956 25 bash sleep shell-script

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

while true; do
  test_condition && break
  sleep 1
done
Run Code Online (Sandbox Code Playgroud)

但它在每次迭代(睡眠)时创建 1 个子流程。我可以通过以下方式避免它们:

while true; do
  test_condition && break
done
Run Code Online (Sandbox Code Playgroud)

但它使用大量 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.
Run Code Online (Sandbox Code Playgroud)

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

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

Ans*_*ann 21

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


Gil*_*il' 10

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

while ! test_condition; do
  sleep 1
done
Run Code Online (Sandbox Code Playgroud)

如果你真的想避免外部进程,你不需要保持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
Run Code Online (Sandbox Code Playgroud)


bol*_*olt 7

我最近需要这样做。我想出了以下函数,它可以让 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 || :
}
Run Code Online (Sandbox Code Playgroud)

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

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

snore 0.1  # sleeps for 0.1 seconds
snore 10   # sleeps for 10 seconds
snore      # sleeps forever
Run Code Online (Sandbox Code Playgroud)

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