11 votes

Boucles parallèles de coquilles

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

for i in *.mesfichiers; faire faire_quelque_chose $i `paramètres_dérivés $i` autres_paramètres; fait

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

> function pwait() {
>     while [ $(jobs -p | wc -l) -ge $1 ]; faire
>         dormir 1
>     faire
>}
>

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 *; faire faire_quelque_chose $i & pwait 10 fait

Mais cela ne fonctionne pas très bien, par exemple j'ai essayé avec une boucle for convertissant de nombreux fichiers mais cela m'a donné une erreur et laissé des tâches inachevées.

Je ne peux pas croire que cela n'ait pas déjà été fait étant donné que la discussion sur la liste de diffusion de zsh est si ancienne maintenant. Alors, connaissez-vous une meilleure solution ?

15voto

Un makefile est une bonne solution à votre problème. Vous pourriez 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 lancer les tâches et de détecter leur fin, mais également de gérer l'équilibrage de charge, ce qui n'est pas simple.

L'exigence de globbing n'est pas un obstacle : il existe des implémentations de make qui le prennent en charge. GNU make, qui prend en charge l'expansion des jokers tels que $(wildcard *.c) et l'accès au shell tel que $(shell mycommand) (consultez le manuel de GNU make pour plus d'informations sur les fonctions). C'est le make par défaut sur Linux, et disponible sur la plupart des autres systèmes. Voici un squelette de Makefile que vous pourrez peut-être adapter à vos besoins :

sources = $(wildcard \*.src)

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

%.tgt: %.src
    do\_something $< $$(derived\_params $<) >$@

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

8voto

Ole Tange Points 426

Je ne suis pas sûr de ce à quoi ressemblent vos arguments dérivés. Mais avec GNU Parallel http:// www.gnu.org/software/parallel/ vous pouvez faire cela pour exécuter un travail par noyau de CPU :

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

Si ce que vous voulez dériver consiste simplement à changer l'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

7voto

Damian Powell Points 315

Ne serait-il pas plus judicieux d'utiliser la commande wait de votre shell pour cela?

for i in *
do
    do_something $i &
done
wait

Votre boucle exécute une tâche puis attend, puis passe à la tâche suivante. Si ce qui précède ne fonctionne pas pour vous, alors la vôtre pourrait mieux fonctionner si vous déplacez pwait après done.

3voto

zebediah49 Points 513

Pourquoi personne n'a encore mentionné xargs?

En supposant que vous avez exactement trois arguments,

pour i in *.mesfichiers; faire 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):

pour i in *.mesfichiers; faire 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 null, puis le nombre de paramètres doit être spécifié avec le xargs -n.

0voto

vegabondx Points 1

J'ai essayé certaines des réponses. Elles rendent le script un peu plus complexe que ce qui est nécessaire. Idéalement, l'utilisation de parallel ou de xargs serait préférable, cependant, si les opérations à l'intérieur de la boucle for sont compliquées, il pourrait être problématique de créer de gros et longs fichiers de lignes à fournir à parallel. Au lieu de cela, nous pourrions utiliser source comme suit

# Créer un fichier de test
$ cat test.txt
task_test 1
task_test 2

# Créer un fichier source shell
$ cat task.sh
task_test()
{
    echo $1
}

# Utiliser la source sous bash -c
$ cat test.txt | xargs -n1 -I{} bash -c 'source task.sh; {}'
1
2

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

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

Définir quelque chose comme do_something.sh

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

}

exécutez avec xarg ou parallel gnu

   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.

SistemesEz.com

SystemesEZ est une communauté de sysadmins où vous pouvez résoudre vos problèmes et vos doutes. Vous pouvez consulter les questions des autres sysadmins, poser vos propres questions ou résoudre celles des autres.

Powered by:

X