Question

Disons que j'ai une boucle en Bash:

for foo in `some-command`
do
   do-something $foo
done

do-something est en cpu, et j'ai un beau brillant à 4 cœurs du processeur.J'aimerais être capable d'exécuter jusqu'à 4 do-something's à la fois.

L'approche naïve semble être:

for foo in `some-command`
do
   do-something $foo &
done

Cela va lancer tous do-somethings à la fois, mais il y a quelques inconvénients, surtout que quelque chose peut aussi avoir une importante I/O duquel l'exécution de la tous à la fois pourrait ralentir un peu.L'autre problème, c'est que ce bloc de code renvoie immédiatement, donc pas moyen de faire d'autres travaux lorsque tous les do-somethings sont finis.

Comment écririez-vous de cette boucle, il y a donc toujours X do-somethings running à la fois?

Était-ce utile?

La solution

En fonction de ce que vous voulez faire xargs peut aussi aider (ici:la conversion de documents avec pdf2ps):

cpus=$( ls -d /sys/devices/system/cpu/cpu[[:digit:]]* | wc -w )

find . -name \*.pdf | xargs --max-args=1 --max-procs=$cpus  pdf2ps

À partir de la documentation:

--max-procs=max-procs
-P 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 with -P; otherwise chances are that only one
       exec will be done.

Autres conseils

Avec GNU Parallèle http://www.gnu.org/software/parallel/ vous pouvez écrire:

some-command | parallel do-something

GNU Parallèle prend également en charge l'exécution de travaux sur des ordinateurs distants.Ce sera exécuté par un PROCESSEUR de base sur les ordinateurs distants, même si elles peuvent avoir un nombre différent de noyaux:

some-command | parallel -S server1,server2 do-something

Un exemple plus complexe:Ici, nous avons la liste des fichiers que nous voulons my_script pour exécuter sur.Les fichiers ont l'extension (peut-être .jpeg).Nous voulons que la sortie de my_script être mis en regard des fichiers dans basename.(p. ex.foo.jpeg -> foo.out).Nous voulons exécuter my_script une fois pour chaque cœur de l'ordinateur a et nous voulons exécuter sur l'ordinateur local, trop.Pour les ordinateurs à distance nous voulons que le dossier soit traité transférées à l'ordinateur.Lorsque my_script finitions, nous voulons foo.hors transférés ensuite, nous voulons foo.jpeg et toto.hors supprimé de l'ordinateur distant:

cat list_of_files | \
parallel --trc {.}.out -S server1,server2,: \
"my_script {} > {.}.out"

GNU Parallèle permet de s'assurer de la sortie de chaque tâche n'est pas de mélanger, de sorte que vous pouvez utiliser la sortie comme entrée pour un autre programme:

some-command | parallel do-something | postprocess

Voir les vidéos pour plus d'exemples: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

maxjobs=4
parallelize () {
        while [ $# -gt 0 ] ; do
                jobcnt=(`jobs -p`)
                if [ ${#jobcnt[@]} -lt $maxjobs ] ; then
                        do-something $1 &
                        shift  
                else
                        sleep 1
                fi
        done
        wait
}

parallelize arg1 arg2 "5 args to third job" arg4 ...

Au lieu d'une plaine bash, utiliser un Makefile, puis spécifiez le nombre de simultanée des emplois avec make -jX où X est le nombre de travaux à exécuter à la fois.

Ou vous pouvez utiliser wait ("man wait"):lancer plusieurs processus fils, appel wait - elle va sortir quand l'enfant les processus de finition.

maxjobs = 10

foreach line in `cat file.txt` {
 jobsrunning = 0
 while jobsrunning < maxjobs {
  do job &
  jobsrunning += 1
 }
wait
}

job ( ){
...
}

Si vous avez besoin de stocker l'emploi du résultat, puis d'affecter le résultat à une variable.Après wait vous venez de vérifier que la variable contient.

Voici une solution alternative qui peut être inséré dans .bashrc et utilisés pour tous les jours un liner:

function pwait() {
    while [ $(jobs -p | wc -l) -ge $1 ]; do
        sleep 1
    done
}

Pour l'utiliser, tout ce qu'on a à faire est de mettre & après les travaux et d'un pwait appel, le paramètre donne le nombre de processus parallèles:

for i in *; do
    do_something $i &
    pwait 10
done

Il serait plus agréable à utiliser wait au lieu de occupé attente sur la sortie de jobs -p, mais il ne semble pas être une solution évidente à attendre jusqu'à une de ces emplois est fini au lieu d'un tous.

Peut-être essayer un parallélisation d'utilité au lieu de réécrire la boucle?Je suis un grand fan de xjobs.J'utilise xjobs tout le temps à la messe de copier des fichiers à travers notre réseau, généralement lors de la configuration d'un nouveau serveur de base de données.http://www.maier-komor.de/xjobs.html

Tout en faisant ce droit dans bash est probablement impossible, vous pouvez faire une demi-droite assez facilement. bstark a donné une juste approximation de droit, mais le son a la suite de défauts:

  • Couper un mot:Vous ne pouvez pas passer toutes les emplois que d'utiliser les caractères suivants dans leurs arguments:des espaces, des tabulations, retours à la ligne, des étoiles, des points d'interrogation.Si vous le faites, les choses vont casser, éventuellement de façon inattendue.
  • Il s'appuie sur le reste de votre script à pas de fond quoi que ce soit.Si vous le faites, ou plus tard, vous ajouter quelque chose pour le script qui est envoyé par le fond, car vous avez oublié que vous n'étiez pas autorisé à utiliser backgrounded emploi en raison de son extrait, les choses vont se briser.

Un autre rapprochement qui n'a pas ces défauts est la suivante:

scheduleAll() {
    local job i=0 max=4 pids=()

    for job; do
        (( ++i % max == 0 )) && {
            wait "${pids[@]}"
            pids=()
        }

        bash -c "$job" & pids+=("$!")
    done

    wait "${pids[@]}"
}

Notez que celui-ci est facilement adaptable à vérifier aussi le code de sortie de chaque travail que ça se termine ainsi, vous pouvez alerter l'utilisateur si un travail d'échec ou de définir un code de sortie pour scheduleAll selon le nombre d'emplois qui a échoué, ou quelque chose.

Le problème avec ce code, c'est juste que:

  • L'horaire de quatre (dans ce cas) les emplois à un moment et puis attend tous les quatre à la fin.Certains sont peut-être fait plus tôt que d'autres, qui sera la cause de la prochaine fournée de quatre emplois d'attendre jusqu'à ce que le plus long du lot précédent est terminé.

Une solution qui prend en charge ce dernier devra utiliser kill -0 de scrutin si l'un de ces procédés ont disparu à la place de la wait et le calendrier de travail suivant.Cependant, qui introduit un nouveau problème:vous avez une condition de concurrence entre un travail de fin, et la kill -0 vérifier si il est terminé.Si la fin de l'emploi et un autre processus sur votre système démarre en même temps, en prenant un hasard PID qui se trouve être celui de l'emploi qui vient de terminer, la kill -0 ne pas l'avis de votre travail d'avoir fini, et les choses vont briser à nouveau.

Une solution parfaite n'est pas possible dans bash.

Si vous êtes familier avec le make de commande, la plupart du temps, vous pouvez exprimer la liste des commandes que vous souhaitez exécuter comme un makefile.Par exemple, si vous avez besoin d'exécuter $SOME_COMMAND sur les fichiers *.d'entrée dont chacune produit *.de sortie, vous pouvez utiliser le makefile

INPUT  = a.input b.input
OUTPUT = $(INPUT:.input=.output)

%.output : %.input
    $(SOME_COMMAND) $< $@

all: $(OUTPUT)

et puis il suffit d'exécuter

make -j<NUMBER>

pour exécuter au plus grand NOMBRE des commandes en parallèle.

fonction pour bash:

parallel ()
{
    awk "BEGIN{print \"all: ALL_TARGETS\\n\"}{print \"TARGET_\"NR\":\\n\\t@-\"\$0\"\\n\"}END{printf \"ALL_TARGETS:\";for(i=1;i<=NR;i++){printf \" TARGET_%d\",i};print\"\\n\"}" | make $@ -f - all
}

à l'aide de:

cat my_commands | parallel -j 4

Le projet que je travaille sur les utilisations de l' attendre commande de contrôle parallèle shell (ksh en fait) des processus.Pour répondre à vos préoccupations au sujet de IO, sur un système d'exploitation moderne, il est possible de l'exécution en parallèle sera effectivement augmenter l'efficacité.Si tous les processus de la lecture des mêmes blocs sur le disque, seul le premier aura pour frapper le matériel physique.Les autres processus seront souvent en mesure de récupérer le bloc à partir de l'OS du disque de cache en mémoire.Évidemment, la lecture de la mémoire est de plusieurs ordres de grandeur plus rapide que la lecture à partir du disque.Aussi, l'avantage ne nécessite aucun codage des changements.

Ce pourrait être assez bon pour la plupart des besoins, mais n'est pas optimale.

#!/bin/bash

n=0
maxjobs=10

for i in *.m4a ; do
    # ( DO SOMETHING ) &

    # limit jobs
    if (( $(($((++n)) % $maxjobs)) == 0 )) ; then
        wait # wait until all have finished (not optimal, but most times good enough)
        echo $n wait
    fi
done

Vraiment en retard à la fête, voici une autre solution.

Beaucoup de solutions ne gère pas les espaces et les caractères spéciaux dans les commandes, ne le gardez pas N travaux en cours d'exécution à tout moment, manger de la cpu dans les boucles, ou de s'appuyer sur les dépendances externes (par ex.GNU parallel).

Avec source d'inspiration pour les morts/zombie la gestion de processus, voici un pur bash solution:

function run_parallel_jobs {
    local concurrent_max=$1
    local callback=$2
    local cmds=("${@:3}")
    local jobs=( )

    while [[ "${#cmds[@]}" -gt 0 ]] || [[ "${#jobs[@]}" -gt 0 ]]; do
        while [[ "${#jobs[@]}" -lt $concurrent_max ]] && [[ "${#cmds[@]}" -gt 0 ]]; do
            local cmd="${cmds[0]}"
            cmds=("${cmds[@]:1}")

            bash -c "$cmd" &
            jobs+=($!)
        done

        local job="${jobs[0]}"
        jobs=("${jobs[@]:1}")

        local state="$(ps -p $job -o state= 2>/dev/null)"

        if [[ "$state" == "D" ]] || [[ "$state" == "Z" ]]; then
            $callback $job
        else
            wait $job
            $callback $job $?
        fi
    done
}

Et exemple d'utilisation:

function job_done {
    if [[ $# -lt 2 ]]; then
        echo "PID $1 died unexpectedly"
    else
        echo "PID $1 exited $2"
    fi
}

cmds=( \
    "echo 1; sleep 1; exit 1" \
    "echo 2; sleep 2; exit 2" \
    "echo 3; sleep 3; exit 3" \
    "echo 4; sleep 4; exit 4" \
    "echo 5; sleep 5; exit 5" \
)

# cpus="$(getconf _NPROCESSORS_ONLN)"
cpus=3
run_parallel_jobs $cpus "job_done" "${cmds[@]}"

La sortie:

1
2
3
PID 56712 exited 1
4
PID 56713 exited 2
5
PID 56714 exited 3
PID 56720 exited 4
PID 56724 exited 5

Pour chaque processus de sortie de la manipulation $$ pourrait être utilisé pour se connecter à un fichier, par exemple:

function job_done {
    cat "$1.log"
}

cmds=( \
    "echo 1 \$\$ >\$\$.log" \
    "echo 2 \$\$ >\$\$.log" \
)

run_parallel_jobs 2 "job_done" "${cmds[@]}"

Sortie:

1 56871
2 56872

Vous pouvez utiliser un simple imbriqués pour la boucle (substitut approprié des entiers N et M ci-dessous):

for i in {1..N}; do
  (for j in {1..M}; do do_something; done & );
done

Cela permettra d'exécuter faire_quelque_chose N*M temps dans la M des tours, chaque tour de la N de l'exécution de travaux en parallèle.Vous pouvez faire N égal au nombre de Processeurs que vous avez.

Voici comment j'ai réussi à résoudre ce problème dans un script bash:

 #! /bin/bash

 MAX_JOBS=32

 FILE_LIST=($(cat ${1}))

 echo Length ${#FILE_LIST[@]}

 for ((INDEX=0; INDEX < ${#FILE_LIST[@]}; INDEX=$((${INDEX}+${MAX_JOBS})) ));
 do
     JOBS_RUNNING=0
     while ((JOBS_RUNNING < MAX_JOBS))
     do
         I=$((${INDEX}+${JOBS_RUNNING}))
         FILE=${FILE_LIST[${I}]}
         if [ "$FILE" != "" ];then
             echo $JOBS_RUNNING $FILE
             ./M22Checker ${FILE} &
         else
             echo $JOBS_RUNNING NULL &
         fi
         JOBS_RUNNING=$((JOBS_RUNNING+1))
     done
     wait
 done

Ma solution pour toujours garder un certain nombre de processus en cours d'exécution, garder le suivi des erreurs et les traiter ubnterruptible / zombie processus:

function log {
    echo "$1"
}

# Take a list of commands to run, runs them sequentially with numberOfProcesses commands simultaneously runs
# Returns the number of non zero exit codes from commands
function ParallelExec {
    local numberOfProcesses="${1}" # Number of simultaneous commands to run
    local commandsArg="${2}" # Semi-colon separated list of commands

    local pid
    local runningPids=0
    local counter=0
    local commandsArray
    local pidsArray
    local newPidsArray
    local retval
    local retvalAll=0
    local pidState
    local commandsArrayPid

    IFS=';' read -r -a commandsArray <<< "$commandsArg"

    log "Runnning ${#commandsArray[@]} commands in $numberOfProcesses simultaneous processes."

    while [ $counter -lt "${#commandsArray[@]}" ] || [ ${#pidsArray[@]} -gt 0 ]; do

        while [ $counter -lt "${#commandsArray[@]}" ] && [ ${#pidsArray[@]} -lt $numberOfProcesses ]; do
            log "Running command [${commandsArray[$counter]}]."
            eval "${commandsArray[$counter]}" &
            pid=$!
            pidsArray+=($pid)
            commandsArrayPid[$pid]="${commandsArray[$counter]}"
            counter=$((counter+1))
        done


        newPidsArray=()
        for pid in "${pidsArray[@]}"; do
            # Handle uninterruptible sleep state or zombies by ommiting them from running process array (How to kill that is already dead ? :)
            if kill -0 $pid > /dev/null 2>&1; then
                pidState=$(ps -p$pid -o state= 2 > /dev/null)
                if [ "$pidState" != "D" ] && [ "$pidState" != "Z" ]; then
                    newPidsArray+=($pid)
                fi
            else
                # pid is dead, get it's exit code from wait command
                wait $pid
                retval=$?
                if [ $retval -ne 0 ]; then
                    log "Command [${commandsArrayPid[$pid]}] failed with exit code [$retval]."
                    retvalAll=$((retvalAll+1))
                fi
            fi
        done
        pidsArray=("${newPidsArray[@]}")

        # Add a trivial sleep time so bash won't eat all CPU
        sleep .05
    done

    return $retvalAll
}

Utilisation:

cmds="du -csh /var;du -csh /tmp;sleep 3;du -csh /root;sleep 10; du -csh /home"

# Execute 2 processes at a time
ParallelExec 2 "$cmds"

# Execute 4 processes at a time
ParallelExec 4 "$cmds"

$DOMAINES = "liste de quelques-uns de domaine dans les commandes" pour les foo dans some-command faire

eval `some-command for $DOMAINS` &

    job[$i]=$!

    i=$(( i + 1))

fait

Ndomains=echo $DOMAINS |wc -w

for i in $(seq 1 1 $Ndomains) faire echo "attendez ${travail[$i]}" wait "${travail[$i]}" fait

dans ce concept de travail pour la parallélisation.important, c'est la dernière ligne de la fonction eval est '&' qui va mettre les commandes à l'arrière.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top