Issue
I need a bash script to run some jobs in the background, three jobs at a time.
I know can do this in the following way, and for illustration, I will assume the number of jobs is 6:
./j1 &
./j2 &
./j3 &
wait
./j4 &
./j5 &
./j6 &
wait
However, this way, if, for example, j2 takes a lot longer to run that j1 and j3, then, I will be stuck with only one background job running for a long time.
The alternative (which is what I want) is that whenever one job is completed, bash should start the next job in the queue so that a rate of 3 jobs at any given time is maintained. Is it possible to write a bash script to implement this alternative, possibly using a loop? Please note that I need to run far more jobs, and I expect this alternative method to save me a lot of time.
Here is my draft of the script, which I hope you can help me to verify its correctness and improve it, as I'm new to scripting in bash. The ideas in this script are taken and modified from here, here, and here):
for i in $(seq 6)
do
# wait here if the number of jobs is 3 (or more)
while (( (( $(jobs -p | wc -l) )) >= 3 ))
do
sleep 5 # check again after 5 seconds
done
jobs -x ./j$i &
done
wait
IMHO, I think this script does the required behavior. However, I need to know -from bash experts- if I'm doing something wrong or if there is a better way of implementing this idea.
Thank you very much.
Solution
With GNU xargs:
printf '%s\0' j{1..6} | xargs -0 -n1 -P3 sh -c './"$1"' _
With bash (4.x) builtins:
max_jobs=3; cur_jobs=0
for ((i=0; i<6; i++)); do
# If true, wait until the next background job finishes to continue.
((cur_jobs >= max_jobs)) && wait -n
# Increment the current number of jobs running.
./j"$i" & ((++cur_jobs))
done
wait
Note that the approach relying on builtins has some corner cases -- if you have multiple jobs exiting at the exact same time, a single wait -n
can reap several of them, thus effectively consuming multiple slots. If we wanted to be more robust, we might end up with something like the following:
max_jobs=3
declare -A cur_jobs=( ) # build an associative array w/ PIDs of jobs we started
for ((i=0; i<6; i++)); do
if (( ${#cur_jobs[@]} >= max_jobs )); then
wait -n # wait for at least one job to exit
# ...and then remove any jobs that aren't running from the table
for pid in "${!cur_jobs[@]}"; do
kill -0 "$pid" 2>/dev/null && unset cur_jobs[$pid]
done
fi
./j"$i" & cur_jobs[$!]=1
done
wait
...which is obviously a lot of work, and still has a minor race. Consider using xargs -P
instead. :)
Answered By - Charles Duffy Answer Checked By - Clifford M. (WPSolving Volunteer)