Disons que j'ai une boucle dans Bash:
for foo in `some-command`
do
do-something $foo
done
do-something
est lié au processeur et j'ai un beau processeur 4 cœurs brillant. J'aimerais pouvoir exécuter jusqu'à 4 do-something
à la fois.
L'approche naïve semble être:
for foo in `some-command`
do
do-something $foo &
done
Cela se déroulera tous do-something
s à la fois, mais il y a quelques inconvénients, surtout que font-quelque chose peut aussi avoir une certaine E / S important qui effectuant tout à la fois pourrait ralentir un peu. L'autre problème est que ce bloc de code retourne immédiatement, donc aucun moyen de faire d'autres travaux lorsque tous les do-something
s sont terminés.
Comment écririez-vous cette boucle pour qu'il y ait toujours X do-something
s en cours d'exécution à la fois?
Réponses:
En fonction de ce que vous voulez faire, xargs peut également vous aider (ici: convertir des 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.
la source
find [...] -print0
etxargs -0
.cpus=$(getconf _NPROCESSORS_ONLN)
--max-procs=0
pour obtenir le plus de processus possible?--max-procs=0
ressemble plus à la tentative du questionneur (lancer autant de processus que d'arguments).Avec GNU Parallel http://www.gnu.org/software/parallel/, vous pouvez écrire:
GNU Parallel prend également en charge l'exécution de travaux sur des ordinateurs distants. Cela en exécutera un par cœur de processeur sur les ordinateurs distants, même s'ils ont un nombre de cœurs différent:
Un exemple plus avancé: Ici, nous listons les fichiers sur lesquels nous voulons que my_script s'exécute. Les fichiers ont l'extension (peut-être .jpeg). Nous voulons que la sortie de my_script soit placée à côté des fichiers dans basename.out (par exemple foo.jpeg -> foo.out). Nous voulons exécuter my_script une fois pour chaque noyau de l'ordinateur et nous voulons également l'exécuter sur l'ordinateur local. Pour les ordinateurs distants, nous voulons que le fichier soit traité et transféré vers l'ordinateur donné. Lorsque my_script se termine, nous voulons que foo.out soit transféré et nous voulons ensuite que foo.jpeg et foo.out soient supprimés de l'ordinateur distant:
cat list_of_files | \ parallel --trc {.}.out -S server1,server2,: \ "my_script {} > {.}.out"
GNU Parallel s'assure que la sortie de chaque travail ne se mélange pas, vous pouvez donc utiliser la sortie comme entrée pour un autre programme:
Voir les vidéos pour plus d'exemples: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1
la source
find
commande pour générer une liste de fichiers, car cela évite non seulement le problème lorsqu'il y a un espace dans un nom de fichier qui se produitfor i in ...; do
mais find peut également fairefind -name \*.extension1 -or -name \*.extension2
ce que les parallèles GNU {.} Peuvent gérer très bien.cat
est, bien sûr, inutile.la source
Voici une solution alternative qui peut être insérée dans .bashrc et utilisée pour un liner quotidien:
function pwait() { while [ $(jobs -p | wc -l) -ge $1 ]; do sleep 1 done }
Pour l'utiliser, il suffit de mettre
&
après les jobs et un appel pwait, le paramètre donne le nombre de processus parallèles:for i in *; do do_something $i & pwait 10 done
Ce serait plus agréable à utiliser
wait
au lieu d'être occupé à attendre la sortie dejobs -p
, mais il ne semble pas y avoir de solution évidente pour attendre que l'un des travaux donnés soit terminé au lieu de tous.la source
Au lieu d'un simple bash, utilisez un Makefile, puis spécifiez le nombre de travaux simultanés avec
make -jX
où X est le nombre de travaux à exécuter à la fois.Ou vous pouvez utiliser
wait
("man wait
"): lancez plusieurs processus enfants, appelezwait
- il se fermera lorsque les processus enfants seront terminés.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 le résultat de la tâche, affectez leur résultat à une variable. Après avoir
wait
vérifié ce que contient la variable.la source
Essayez peut-être un utilitaire de parallélisation au lieu de réécrire la boucle? Je suis un grand fan de xjobs. J'utilise xjobs tout le temps pour copier en masse des fichiers sur 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
la source
Si vous êtes familier avec la
make
commande, la plupart du temps, vous pouvez exprimer la liste des commandes que vous souhaitez exécuter en tant que fichier Make. Par exemple, si vous devez exécuter $ SOME_COMMAND sur des fichiers * .input dont chacun produit * .output, vous pouvez utiliser le makefileet puis juste courir
pour exécuter au maximum NUMBER commandes en parallèle.
la source
Bien qu'il
bash
soit probablement impossible de faire cela directement , vous pouvez faire un semi-droit assez facilement.bstark
a donné une bonne approximation du droit mais le sien présente les défauts suivants:Une autre approximation 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 pour vérifier également le code de sortie de chaque travail à la fin afin que vous puissiez avertir l'utilisateur si un travail échoue ou définir un code de sortie pour en
scheduleAll
fonction du nombre de travaux qui ont échoué, ou quelque chose.Le problème avec ce code est juste que:
Une solution qui prend en charge ce dernier problème devrait utiliser
kill -0
pour interroger si l'un des processus a disparu au lieu dewait
et planifier le travail suivant. Cependant, cela introduit un petit problème nouveau: vous avez une condition de concurrence entre la fin d'un travail et lakill -0
vérification de sa fin. Si le travail se termine et qu'un autre processus sur votre système démarre en même temps, en prenant un PID aléatoire qui se trouve être celui du travail qui vient de se terminer, lekill -0
ne remarquera pas que votre travail est terminé et les choses se casseront à nouveau.Une solution parfaite n'est pas possible dans
bash
.la source
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 }
en utilisant:
la source
make -j
est intelligente, mais sans aucune explication et cette goutte de code Awk en écriture seule, je m'abstiens de voter.Le projet sur lequel je travaille utilise la commande wait pour contrôler les processus shell parallèles (ksh en fait). Pour répondre à vos préoccupations concernant les E / S, sur un système d'exploitation moderne, il est possible qu'une exécution parallèle augmente réellement l'efficacité. Si tous les processus lisent les mêmes blocs sur le disque, seul le premier processus devra atteindre le matériel physique. Les autres processus pourront souvent récupérer le bloc du cache disque du système d'exploitation en mémoire. De toute évidence, la lecture à partir de la mémoire est plusieurs ordres de grandeur plus rapide que la lecture à partir du disque. En outre, l'avantage ne nécessite aucune modification de codage.
la source
Cela peut être suffisant pour la plupart des applications, mais ce n'est pas optimal.
#!/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
la source
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
la source
Vraiment en retard à la fête ici, mais voici une autre solution.
Beaucoup de solutions ne gèrent pas les espaces / caractères spéciaux dans les commandes, ne font pas fonctionner N jobs à tout moment, mangent le processeur dans des boucles occupées ou s'appuient sur des dépendances externes (par exemple GNU
parallel
).Avec l' inspiration pour la gestion des processus morts / zombies , voici une solution pure bash:
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[@]}"
Le résultat:
La gestion de la sortie par processus
$$
peut être utilisée 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[@]}"
Production:
la source
Vous pouvez utiliser une simple boucle for imbriquée (remplacez les entiers appropriés par N et M ci-dessous):
for i in {1..N}; do (for j in {1..M}; do do_something; done & ); done
Cela exécutera do_something N * M fois en M tours, chaque tour exécutant N travaux en parallèle. Vous pouvez rendre N égal au nombre de processeurs dont vous disposez.
la source
Ma solution pour toujours garder un nombre donné de processus en cours d'exécution, suivre les erreurs et gérer les processus ubnterruptibles / zombies:
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 }
Usage:
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"
la source
$ DOMAINS = "liste de certains domaines dans les commandes" pour foo in
some-command
doeval `some-command for $DOMAINS` & job[$i]=$! i=$(( i + 1))
terminé
Ndomaines =
echo $DOMAINS |wc -w
for i in $ (seq 1 1 $ Ndomains) do echo "wait for $ {job [$ i]}" wait "$ {job [$ i]}" done
dans ce concept fonctionnera pour la parallélisation. la chose importante est que la dernière ligne de eval est «&» qui mettra les commandes aux arrière-plans.
la source