Boucles de coque parallèles

11

Je veux traiter de nombreux fichiers et comme j'ai ici un tas de cœurs, je veux le faire en parallèle:

for i in *.myfiles; do do_something $i `derived_params $i` other_params; done

Je connais une solution Makefile mais mes commandes ont besoin des arguments de la liste de globbing du shell. Ce que j'ai trouvé c'est:

> 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

Mais cela ne fonctionne pas très bien, par exemple, je l'ai essayé avec par exemple une boucle for pour convertir de nombreux fichiers mais en me donnant des erreurs et des travaux non effectués.

Je ne peux pas croire que cela ne soit pas encore fait car la discussion sur la liste de diffusion zsh est si ancienne maintenant. Alors tu sais mieux?

math
la source
Semblable à cette question: superuser.com/questions/153630/… Voyez si cette technique fonctionne pour vous.
JRobert
Il serait utile que vous publiiez les messages d'erreur.
pause jusqu'à nouvel ordre.
@JRobert oui je le savais mais cela n'aide pas vraiment car l'approche makefile ne fonctionnera pas comme je l'ai dit! @Dennis: Ok, je laisse d'abord courir un sommet à côté de me montrer plus que le nombre spécifié de processus. Deuxièmement, il ne revient pas correctement à l'invite. Troisièmement, j'ai dit que cela laisse les travaux non effectués n'était pas correct: je viens de placer un indicateur echo "DONE"après la boucle qui a été exécutée avant que les travaux actifs ne soient terminés. => Cela m'a fait penser que le travail n'était pas fait.
math

Réponses:

15

Un makefile est une bonne solution à votre problème. Vous pouvez programmer cette exécution parallèle dans un shell, mais c'est difficile, comme vous l'avez remarqué. Une implémentation parallèle de make se chargera non seulement de démarrer des travaux et de détecter leur terminaison, mais également de gérer l'équilibrage de charge, ce qui est délicat.

L'exigence de globbing n'est pas un obstacle: il existe des implémentations de make qui le supportent. GNU make, qui a une extension générique telle que $(wildcard *.c)et un accès shell tel que $(shell mycommand)(recherchez les fonctions dans le manuel GNU make pour plus d'informations). C'est la valeur par défaut makesous Linux et disponible sur la plupart des autres systèmes. Voici un squelette Makefile que vous pourrez peut-être adapter à vos besoins:

sources = $ (caractère générique * .src)

tous: $ (sources: .src = .tgt)

% .tgt: $ .src
    do_something $ <$$ (dérivé_params $ <)> $ @

Exécutez quelque chose comme make -j4pour exécuter quatre tâches en parallèle ou make -j -l3pour maintenir la charge moyenne autour de 3.

Gilles 'SO- arrête d'être méchant'
la source
8

Je ne sais pas à quoi ressemblent vos arguments dérivés. Mais avec GNU Parallel http: // www.gnu.org/software/parallel/ vous pouvez le faire pour exécuter un travail par cœur de processeur:

find . | parallel -j+0 'a={}; name=${a##*/}; upper=$(echo "$name" | tr "[:lower:]" "[:upper:]");
   echo "$name - $upper"'

Si vous voulez simplement dériver le .extension, le {.} Peut être pratique:

parallel -j+0 lame {} -o {.}.mp3 ::: *.wav

Regardez la vidéo d'introduction à GNU Parallel sur http://www.youtube.com/watch?v=OpaiGYxkSuQ

Ole Tange
la source
7

Est-ce que l'utilisation de la waitcommande du shell ne fonctionnerait pas pour vous?

for i in *
do
    do_something $i &
done
wait

Votre boucle exécute un travail puis l'attend, puis effectue le travail suivant. Si ce qui précède ne fonctionne pas pour vous, alors le vôtre pourrait mieux fonctionner si vous déménagez pwaitaprès done.

En pause jusqu'à nouvel ordre.
la source
non avec 1 million de fichiers j'aurais 1 million de processus en cours d'exécution, ou je me trompe?
math
1
@brubelsabs: Eh bien, il essaierait de faire un million de processus. Vous n'avez pas dit dans votre question combien de fichiers vous deviez traiter. Je pense que vous auriez besoin d'utiliser des forboucles imbriquées pour limiter cela: for file in *; do for i in {1..10}; do do_something "$i" & done; wait; done(non testé) Cela devrait faire dix à la fois et attendre que les dix de chaque groupe soient terminés avant de commencer les dix suivants. Votre boucle fait un à la fois ce qui est &discutable. Voir la question à laquelle JRobert a lié pour d'autres options. Recherchez sur Stack Overflow d'autres questions similaires aux vôtres (et celle-là).
pause jusqu'à nouvel ordre.
Si le PO prévoit un million de fichiers, il aurait un problème avec for i in *. Il devrait passer des arguments à la boucle avec un tuyau ou quelque chose. Ensuite, au lieu d'une boucle interne, vous pouvez exécuter un compteur d'incrémentation et exécuter "micro-"wait"-s"chaque "$ ((i% 32))" -eq '0'
@DennisWilliamson: la combinaison waitavec une contre-boucle intérieure a bien fonctionné pour moi. Merci!
Joel Purra
3

Pourquoi personne n'a-t-il encore mentionné xargs?

En supposant que vous ayez exactement trois arguments,

for i in *.myfiles; do echo -n $i `derived_params $i` other_params; done | xargs -n 3 -P $PROCS do_something

Sinon, utilisez un délimiteur (null est pratique pour cela):

for i in *.myfiles; do echo -n $i `derived_params $i` other_params; echo -ne "\0"; done | xargs -0 -n 1 -P $PROCS do_something

EDIT: pour ce qui précède, chaque paramètre doit être séparé par un caractère nul, puis le nombre de paramètres doit être spécifié avec xargs -n.

zebediah49
la source
Oui, dans notre projet, quelqu'un a eu la même idée, et cela fonctionne très bien même sous Windows avec MSys.
math
0

J'ai essayé certaines des réponses. Ils rendent le script un peu plus complexe que nécessaire. Idéalement, l'utilisation de parallelou xargsserait préférable, mais si les opérations à l'intérieur de la boucle for sont compliquées, il pourrait être problématique de créer un fichier de grandes et longues lignes à fournir en parallèle. au lieu de cela, nous pourrions utiliser la source comme suit

# Create a test file 
$ cat test.txt
task_test 1
task_test 2

# Create a shell source file 
$ cat task.sh
task_test()
{
    echo $1
}

# use the source under bash -c 
$ cat test.txt | xargs -n1 -I{} bash -c 'source task.sh; {}'
1
2

Ainsi, pour votre problème, la solution ressemblerait à

for i in *.myfiles; echo " do_something $i `derived_params $i` other_params
" >> commands.txt ; done

définir faire quelque chose comme do_something.sh

do_something(){
process $1
echo $2 
whatever $3 

}

exécuter avec xargougnu parallel

   cat commands.txt | xargs -n1 -I{} -P8 bash -c 'source do_something.sh; {}'

Je suppose que l'indépendance fonctionnelle des itérations de for est implicite.

vegabondx
la source