snakeeater38
snakeeater38

Reputation: 43

How can I limit the number of the same background process in bash shell?

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

Answers (2)

ktc
ktc

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

KamilCuk
KamilCuk

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

Related Questions