Reputation: 43
Let's say I have a loop that runs something like the following a large number of times:
while *condition*
do
{
*cool stuff happens here, including running some programs*
} &
done
As it is the system will quickly run out of resources so I want to set a limit (of say, 5 or 10) and then wait until they are done. How can I achieve this?
Upvotes: 1
Views: 396
Reputation: 337
Based on the answer from @KamilCuk (https://stackoverflow.com/a/64393146/5430476), finally, I got this version:
#!/bin/bash
count=0
maxcount=5
for ((i=0; i<10; ++i)); do
{ sleep 0.$i; echo "Job $i done"; } &
count=$((count + 1))
if ((count >= maxcount)); then
wait
count=0
fi
done
# Wait for remaining background processes
wait
Maybe life would get easier if I installed the GNU parallel
like the comments suggested, but what I want to do is to finish a simple backup job in a container. I don't want to install extra commands other than bash as much as possible.
So I wrapped this into a function, like:
#!/bin/bash
job() {
local i=$1 # Job index
shift
local extra_args=("$@")
echo "Job $i started with args: ${extra_args[*]}"
sleep $((RANDOM % 5)) # some works
echo "Job $i finished"
}
parallel_spawn_with_limits() {
local max_limit_per_loop=$1; shift
local job=$1; shift
local count=0
for ((i=0; i<10; ++i)); do
{ "$job" "$i" "$@" & } # Run job in background with arguments
count=$((count + 1))
if ((count >= max_limit_per_loop)); then
wait
count=0
fi
done
wait # Ensure remaining jobs finish
}
Then call like this:
# example usage
parallel_spawn_with_limits 3 job "extra_arg1" "extra_arg2"
[1] 13199
[2] 13200
[3] 13201
Job 1 started with args: extra_arg1 extra_arg2
Job 2 started with args: extra_arg1 extra_arg2
Job 0 started with args: extra_arg1 extra_arg2
Job 0 finished
[1] Done "$job" "$i" "$@"
Job 2 finished
Job 1 finished
[2]- Done "$job" "$i" "$@"
[3]+ Done "$job" "$i" "$@"
# added blank lines for readability
[1] 13479
[2] 13480
Job 3 started with args: extra_arg1 extra_arg2
[3] 13481
Job 4 started with args: extra_arg1 extra_arg2
Job 5 started with args: extra_arg1 extra_arg2
Job 4 finished
Job 5 finished
Job 3 finished
[1] Done "$job" "$i" "$@"
[2]- Done "$job" "$i" "$@"
[3]+ Done "$job" "$i" "$@"
# added blank lines for readability
[1] 14004
[2] 14005
[3] 14006
Job 6 started with args: extra_arg1 extra_arg2
Job 7 started with args: extra_arg1 extra_arg2
Job 8 started with args: extra_arg1 extra_arg2
Job 7 finished
Job 6 finished
[1] Done "$job" "$i" "$@"
[2]- Done "$job" "$i" "$@"
Job 8 finished
[3]+ Done "$job" "$i" "$@"
# added blank lines for readability
[1] 14544
Job 9 started with args: extra_arg1 extra_arg2
Job 9 finished
[1]+ Done "$job" "$i" "$@"
Depending on your needs, you may need to add a trap function or abstract the 10
in the for loop into a new variable.
Upvotes: 0
Reputation: 141155
count=0
maxcount=5
for ((i=0;i<10;++i)); do
{ sleep 0.$i; } &
count=$((count + 1))
if ((count++ > maxcount)); then
wait -n
count=$((count - 1))
fi
done
# wait for the rest of processes
wait
Upvotes: 0