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.

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


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

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 =".


1

Une idée simple:

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


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

1
for i in {1..1000}; do 
     (echo $i ; sleep `expr $RANDOM % 5` ) &
     while [ `jobs | wc -l` -ge 20 ] ; do 
         sleep 1 
     done
done

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 :)

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.