Increasing / decreasing number of xargs parallel processes (at run time!)

xargs makes it very easy to quickly run a set of similar processes in parallel – but did you know when you’re half-way through a long list of tasks it’s possible to change the number of parallel processes that are being used?

It’s there in the man page under “P max-procs, –max-procs=max-procs” but it’s an easy feature to miss if you don’t read all the way through:

-P max-procs, --max-procs=max-procs

Run up to max-procs processes at a time; the default is 1. If max-procs is 0, xargs will run as many processes as possible at a time. Use the -n option or the -L option with -P; otherwise chances are that only one exec will be done. While xargs is running, you can send its process a SIGUSR1 signal to increase the number of commands to run simultaneously, or a SIGUSR2 to decrease the number. You cannot increase it above an implementation-defined limit (which is shown with --show-limits). You cannot decrease it below 1. xargs never terminates its commands; when asked to decrease, it merely waits for more than one existing command to terminate before starting another.

Please note that it is up to the called processes to properly manage parallel access to shared resources. For example, if more than one of them tries to print to stdout, the output will be produced in an indeterminate order (and very likely mixed up) unless the processes collaborate in some way to prevent this. Using some kind of locking scheme is one way to prevent such problems. In general, using a locking scheme will help ensure correct output but reduce performance. If you don't want to tolerate the performance difference, simply arrange for each process to produce a separate output file (or otherwise use separate resources).

What does that look like? Spin up some slow processes and start with 3-way parallel execution:

$ seq 10 30 | xargs -t -I{} -L1 -P3 sleep
sleep 10 
sleep 11 
sleep 12

At this stage we have three processes running:

$ ps auxw | grep -e [s]leep -e [x]args
username 613932 0.0 0.0 8384 2028 pts/3 S+ 12:27 0:00 xargs -t -I{} -L1 -P 3 sleep
username 613933 0.0 0.0 8084 564 pts/3 S+ 12:27 0:00 sleep 10
username 613934 0.0 0.0 8084 560 pts/3 S+ 12:27 0:00 sleep 11
username 613935 0.0 0.0 8084 560 pts/3 S+ 12:27 0:00 sleep 12

Before the first sleep completes, increase the number of parallel processes by three (sendingĀ SIGUSR1 to the xargs process three times); xargs immediately spawns the extra processes:

$ kill -USR1 613932
$ kill -USR1 613932
$ kill -USR1 613932

And we see the new processes being spawned:

$ seq 10 30 | xargs -t -I{} -L1 -P 3 sleep
...
sleep 13 
sleep 14 
sleep 15

$ ps auxw | grep -e [s]leep -e [x]args
username 613932 0.0 0.0 8384 2024 pts/3 S+ 12:27 0:00 xargs -t -I{} -L1 -P 3 sleep
username 613933 0.0 0.0 8084 496 pts/3 S+ 12:27 0:00 sleep 10
username 613934 0.0 0.0 8084 560 pts/3 S+ 12:27 0:00 sleep 11
username 613935 0.0 0.0 8084 564 pts/3 S+ 12:27 0:00 sleep 12
username 613963 0.0 0.0 8084 564 pts/3 S+ 12:27 0:00 sleep 13
username 613964 0.0 0.0 8084 496 pts/3 S+ 12:27 0:00 sleep 14
username 613965 0.0 0.0 8084 560 pts/3 S+ 12:27 0:00 sleep 15

If the number of processes needs to be reduced, send SIGUSR2 to the xargs process the number of times needed. Note that xargs does not kill of ‘excess’ processes, it lets them complete and does not spawn any more parallel processes until the number has dropped below the new limit. The minimum limit is one (so you won’t stop execution by sending SIGUSR2 more times than the current limit):

# Sending more than the limit - the new limit will stop at one:
$ kill -USR2 613932
$ kill -USR2 613932
$ kill -USR2 613932
$ kill -USR2 613932
$ kill -USR2 613932
$ kill -USR2 613932
$ kill -USR2 613932
$ kill -USR2 613932
$ kill -USR2 613932
$ kill -USR2 613932
$ kill -USR2 613932
$ kill -USR2 613932

From here on in – only one process will be running at a time (until the work is complete, or the limit is increased again):

$ ps auxw | grep -e [s]leep -e [x]args
username 613932 0.0 0.0 8384 2028 pts/3 S+ 12:27 0:00 xargs -t -I{} -L1 -P 3 sleep
username 615299 0.0 0.0 8084 560 pts/3 S+ 12:27 0:00 sleep 22

... time passes ...

$ ps auxw | grep -e [s]leep -e [x]args 
username 613932 0.0 0.0 8384 1984 pts/3 S+ 12:34 0:00 xargs -t -I{} -L1 -P 3 sleep
username 615311 0.0 0.0 8084 512 pts/3 S+ 12:37 0:00 sleep 24

Leave a Reply

Your email address will not be published. Required fields are marked *