Exécution de commandes en parallèle avec une limite de nombre simultané de commandes

23

Séquentiel: for i in {1..1000}; do do_something $i; done- trop lent

Parallèle: for i in {1..1000}; do do_something $i& done- trop de charge

Comment exécuter des commandes en parallèle, mais pas plus de, par exemple, 20 instances par moment?

Maintenant, en utilisant généralement hack like for i in {1..1000}; do do_something $i& sleep 5; done, mais ce n'est pas une bonne solution.

Mise à jour 2 : conversion de la réponse acceptée en script: http://vi-server.org/vi/parallel

#!/bin/bash

NUM=$1; shift

if [ -z "$NUM" ]; then
    echo "Usage: parallel <number_of_tasks> command"
    echo "    Sets environment variable i from 1 to number_of_tasks"
    echo "    Defaults to 20 processes at a time, use like \"MAKEOPTS='-j5' parallel ...\" to override."
    echo "Example: parallel 100 'echo \$i; sleep \`echo \$RANDOM/6553 | bc -l\`'"
    exit 1
fi

export CMD="$@";

true ${MAKEOPTS:="-j20"}

cat << EOF | make -f - -s $MAKEOPTS
PHONY=jobs
jobs=\$(shell echo {1..$NUM})

all: \${jobs}

\${jobs}:
        i=\$@ sh -c "\$\$CMD"
EOF

Notez que vous devez remplacer 8 espaces par 2 tabulations avant "i =" pour que cela fonctionne.

Vi.
la source

Réponses:

15

GNU Parallel est fait pour cela.

seq 1 1000 | parallel -j20 do_something

Il peut même exécuter des travaux sur des ordinateurs distants. Voici un exemple pour ré-encoder un MP3 en OGG en utilisant server2 et un ordinateur local exécutant 1 travail par cœur de processeur:

parallel --trc {.}.ogg -j+0 -S server2,: \
     'mpg321 -w - {} | oggenc -q0 - -o {.}.ogg' ::: *.mp3

Regardez une vidéo d'introduction à GNU Parallel ici:

http://www.youtube.com/watch?v=OpaiGYxkSuQ

Ole Tange
la source
Je ne sais pas sur "moreutils" et qu'il existe déjà un outil pour le travail. Regarder et comparer.
Vi.
1
Le parallelin moreutils n'est pas GNU Parallel et est assez limité dans ses options. La commande ci-dessus ne fonctionnera pas avec le parallèle de moreutils.
Ole Tange
1
Une autre option: xargs --max-procs=20.
Vi.
4

Ce n'est pas une solution bash, mais vous devez utiliser un Makefile, éventuellement -lpour ne pas dépasser une charge maximale.

NJOBS=1000

.PHONY = jobs
jobs = $(shell echo {1..$(NJOBS)})

all: $(jobs)

$(jobs):
    do_something $@

Ensuite, pour démarrer 20 emplois à la fois,

$ make -j20

ou pour démarrer autant de travaux que possible sans dépasser une charge de 5

$ make -j -l5
Benjamin Bannier
la source
On dirait que la solution non hacky pour l'instant.
Vi.
2
echo -e 'PHONY=jobs\njobs=$(shell echo {1..100000})\n\nall: ${jobs}\n\n${jobs}:\n\t\techo $@; sleep `echo $$RANDOM/6553 | bc -l`' | make -f - -j20Maintenant, il semble encore plus hacky.
Vi.
@vi: oh my ....
Benjamin Bannier
Converti votre solution en script. Maintenant, il peut être utilisé facilement.
Vi.
2

publier le script dans la question avec mise en forme:

#!/bin/bash

NUM=$1; shift

if [ -z "$NUM" ]; then
    echo "Usage: parallel <number_of_tasks> command"
    echo "    Sets environment variable i from 1 to number_of_tasks"
    echo "    Defaults to 20 processes at a time, use like \"MAKEOPTS='-j5' parallel ...\" to override."
    echo "Example: parallel 100 'echo \$i; sleep \`echo \$RANDOM/6553 | bc -l\`'"
    exit 1
fi

export CMD="$@";

true ${MAKEOPTS:="-j20"}

cat << EOF | make -f - -s $MAKEOPTS
PHONY=jobs
jobs=\$(shell echo {1..$NUM})

all: \${jobs}

\${jobs}:
        i=\$@ sh -c "\$\$CMD"
EOF

Notez que vous devez remplacer 8 espaces par 2 tabulations avant "i =".

warren
la source
1

Une idée simple:

Recherchez i modulo 20 et exécutez la commande shell d'attente avant do_something.

harrymc
la source
Il attendra que toutes les tâches en cours soient terminées (création d'affaissements dans le nombre de tâches) ou attendra une tâche spécifique qui peut se bloquer plus longtemps (en créant à nouveau des affaissements dans ce cas)
Vi.
@Vi: Shell attend pour toutes les tâches d'arrière-plan qui appartiennent à ce shell.
harrymc
1

Vous pouvez utiliser pspour compter le nombre de processus en cours d'exécution et chaque fois que cela tombe en dessous d'un certain seuil, vous démarrez un autre processus.

Pseudo code:

i = 1
MAX_PROCESSES=20
NUM_TASKS=1000
do
  get num_processes using ps
  if num_processes < MAX_PROCESSES
    start process $i
    $i = $i + 1
  endif
  sleep 1 # add this to prevent thrashing with ps
until $i > NUM_TASKS
Paul R
la source
1
for i in {1..1000}; do 
     (echo $i ; sleep `expr $RANDOM % 5` ) &
     while [ `jobs | wc -l` -ge 20 ] ; do 
         sleep 1 
     done
done
msw
la source
Peut-être while [ `jobs | wc -l` -ge 20]; do?
Vi.
bien sûr, mais dans mon exemple, je devrais alors calculer njobsdeux fois, et les performances sont assez importantes dans les scripts shell qui exécutent des tâches de veille;)
msw
Je veux dire que votre version ne fonctionne pas comme prévu. Je passe sleep 1à sleep 0.1et cela commence à faire en moyenne njobs à 40-50 au lieu de 20. S'il y a plus de 20 emplois, nous devons attendre que tout travail soit terminé, pas seulement attendre 1 seconde.
Vi.
0

vous pouvez le faire comme ça.

threads=20
tempfifo=$PMS_HOME/$$.fifo

trap "exec 1000>&-;exec 1000<&-;exit 0" 2
mkfifo $tempfifo
exec 1000<>$tempfifo
rm -rf $tempfifo

for ((i=1; i<=$threads; i++))
do
    echo >&1000
done

for ((j=1; j<=1000; j++))
do
    read -u1000
    {
        echo $j
        echo >&1000
    } &
done

wait
echo "done!!!!!!!!!!"

utilisant des tubes nommés, à chaque fois, il exécute 20 sous-shell en parallèle.

J'espère que cela vous aidera :)

ouyangyewei
la source