避免在没有Bash命令的情况下忙于等待bash


19

我知道我可以在条件变为现实的情况下通过执行以下操作:

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 varfifo的情况下简单地使用它,因为它会消耗stdin,如果stdin不是终端或管道,它将不起作用。

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


1
true是内置的,不会在bash中创建子进程。繁忙的等待永远都是不好的。
jordanm

@joranm:您说得对true,问题已更新。
jfg956

为什么没有fifo?简单地说read -t 1 var
ott--

@ott:您是对的,但这会消耗标准输入。另外,如果stdin不是终端或管道,则它将不起作用。
jfg956

如果可维护性是一个问题,我强烈建议您sleep像第一个示例中那样使用。第二种方法虽然可行,但将来任何人都很难调整。简单的代码也具有更大的安全潜力
库萨兰达

Answers:


17

bash(至少是v2)的较新版本中,可以enable -f filename commandname在运行时(通过)加载内置程序。许多这样的可加载内建函数也随bash源一起分发,并且sleep在其中。当然,不同的操作系统之间的可用性可能会有所不同。例如,在openSUSE上,这些内建函数通过package分发bash-loadables

编辑:修复程序包名称,添加最低bash版本。


哇,这就是我要寻找的东西,而且我绝对可以学到一些关于可加载内置函数的信息:+1。我会尝试的,但这是最好的答案。
jfg956

1
有用 !在debian上,软件包是bash-builtins。它仅包含源代码,并且必须编辑Makefile,但我能够以sleep内置方式安装。谢谢。
jfg956

9

在内部循环中创建大量子流程是一件坏事。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

您认为每秒处理一个花生米是正确的(但是我的问题是想找到一种方法将其去除)。关于较短的版本,它比我的更好,所以+1(但是我删除了mkdir它,因为这样做mktemp(如果不是,这是一个竞争条件))。关于while ! test_condition;哪个也比我的最初解决方案更好的事实也是如此。
jfg956

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 || :
}

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

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

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

我的博客上有一篇写着过多细节的文章


1
优秀的博客文章。但是,我去那里寻求解释,为什么read -t 10 < <(:)read -t 10 <> <(:)等待整整10秒钟时立即返回,但是我仍然不明白。
阿米尔

代表read -t 10 <> <(:)什么<>
CodeMedic

即使基础进程替换<(:)仅允许读取,<>也会打开文件描述符以进行读写。这是一种导致Linux(尤其是Linux)假设有人可能对其进行写操作的黑客行为,因此read会挂起,等待永远不会到达的输入。它不会做到这一点的BSD系统,在这种情况下,解决办法将一命呜呼
螺栓

3

ksh93或中mkshsleep内置有shell,因此一种替代方法是使用这些shell而不是bash

zsh也有一个zselect内置的(内含了zmodload zsh/zselect),可以使用休眠百分之一百秒zselect -t <n>


2

正如用户yoi所说,如果在脚本中打开了stdin,则可以使用以下方法代替sleep 1

read -t 1 3<&- 3<&0 <&3

在Bash 4.1及更高版本中,您可以使用浮点数,例如 read -t 0.3 ...

如果在脚本中关闭了stdin(称为脚本my_script.sh < /dev/null &),则需要使用另一个打开的描述符,例如,在执行读取操作时该描述符不产生输出。标准输出

read -t 1 <&1 3<&- 3<&0 <&3

如果在脚本中所有描述符都已关闭(stdinstdoutstderr)(例如,因为被称为守护程序),那么您需要找到任何不产生输出的现有文件:

read -t 1 </dev/tty10 3<&- 3<&0 <&3

read -t 1 3<&- 3<&0 <&3与相同read -t 0。它只是从标准输入中读取超时信息。
斯特凡Chazelas

1

这可以从登录外壳程序以及非交互式外壳程序中工作。

#!/bin/sh

# to avoid starting /bin/sleep each time we call sleep, 
# make our own using the read built in function
xsleep()
{
  read -t $1 -u 1
}

# usage
xsleep 3

这也工作在Mac OS X v10.12.6
B01

1
不建议这样做。如果多个脚本同时使用此脚本,则它们都将获取SIGSTOP,因为它们都尝试读取stdin。您的标准输入在此等待期间被阻止。不要为此使用stdin。您需要新的不同文件描述符。
Normadize

1
@Normadize此处还有另一个答案(unix.stackexchange.com/a/407383/147685),涉及使用自由文件描述符的问题。其最低最低版本为read -t 10 <> <(:)
阿米尔

0

您真的需要FIFO吗?将stdin重定向到另一个文件描述符也应该起作用。

{
echo line | while read line; do
   read -t 1 <&3
   echo "$line"
done
} 3<&- 3<&0

启发:在while循环内以bash读取输入


1
这不是在睡觉,这仍然是从终端消耗标准输入。
jfg956

0

对上述解决方案略有改进(我以此为基础)。

bash_sleep() {
    read -rt "${1?Specify sleep interval in seconds}" -u 1 <<<"" || :;
}

# sleep for 10 seconds
bash_sleep 10

减少了对fifo的需求,因此无需清理。


1
不建议这样做。如果多个脚本同时使用此脚本,则它们都将获取SIGSTOP,因为它们都尝试读取stdin。您的标准输入在此等待期间被阻止。不要为此使用stdin。您需要新的不同文件描述符。
Normadize

@Normadize没想到这一点;请您详细说明或指向我可以参考的资源。
CodeMedic

@CodeMedic这里还有另一个答案(unix.stackexchange.com/a/407383/147685),涉及使用自由文件描述符的问题。其最低最低版本为read -t 10 <> <(:)
阿米尔
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.