Avoiding busy waiting in bash, without the sleep command

I know I can wait on a condition to become true in bash by doing:

while true; do
  test_condition && break
  sleep 1
done

But it creates 1 sub-process at each iteration (sleep). I could avoid them by doing:

while true; do
  test_condition && break
done

But it uses lot of CPU (busy waiting). To avoid sub-processes and busy waiting, I came up with the solution bellow, but I find it ugly:

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.

Note: in the general case, I cannot simply use read -t 1 var without the fifo, because it will consume stdin, and will not work if stdin is not a terminal or a pipe.

Can I avoid sub-processes and busy waiting in a more elegant way ?

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

In newer versions of bash (at least v2), builtins may be loaded (via enable -f filename commandname) at runtime. A number of such loadable builtins is also distributed with the bash sources, and sleep is among them. Availability may differ from OS to OS (and even machine to machine), of course. For example, on openSUSE, these builtins are distributed via the package bash-loadables.

Method 2

Creating a lot of subprocesses is a bad thing in an inner loop. Creating one sleep process per second is OK. There’s nothing wrong with

while ! test_condition; do
  sleep 1
done

If you really want to avoid the external process, you don’t need to keep the fifo open.

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

Method 3

I recently had a need to do this. I came up with the following function that will allow bash to sleep forever without calling any external program:

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

NOTE: I previously posted a version of this that would open and close the file descriptor each time, but I found that on some systems doing this hundreds of times a second would eventually lock up. Thus the new solution keeps the file descriptor between calls to the function. Bash will clean it up on exit anyway.

This can be called just like /bin/sleep, and it will sleep for the requested time. Called without parameters, it will hang forever.

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

There’s a writeup with excessive details on my blog here

Method 4

As user yoi said, if in your script is stdin opened, then instead of sleep 1 you can simply use:

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

In Bash version 4.1 and newer you can use float number, e.g. read -t 0.3 ...

If in a script stdin is closed (script is called my_script.sh < /dev/null &), then you need use another opened descriptor, which not produces output when read is executed, eg. stdout:

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

If in a script all descriptor is closed (stdin, stdout, stderr) (e.g. because is called as daemon), then you need find any exists file which not produces output:

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

Method 5

In ksh93 or mksh, sleep is a shell builtin, so an alternative might be to use those shells instead of bash.

zsh also has a zselect builtin (loaded with zmodload zsh/zselect) that can sleep for a given number of hundredths of seconds with zselect -t <n>.

Method 6

This works from a login shell as well as a non-interactive shell.

#!/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

Method 7

Do you really need a fifo? Redirecting stdin to another file descriptor should work as well.

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

Inspired by: Read input in bash inside a while loop

Method 8

A slight improvement on the above mentioned solutions (which I have based this on).

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

# sleep for 10 seconds
bash_sleep 10

Reduced the need for a fifo and hence no cleaning up to do.


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x