Quattro compiti in parallelo ... come posso farlo?


23

Ho un sacco di immagini PNG su una directory. Ho un'applicazione chiamata pngout che eseguo per comprimere queste immagini. Questa applicazione è chiamata da uno script che ho fatto. Il problema è che questo script fa uno alla volta, qualcosa del genere:

FILES=(./*.png)
for f in  "${FILES[@]}"
do
        echo "Processing $f file..."
        # take action on each file. $f store current file name
        ./pngout -s0 $f R${f/\.\//}
done

L'elaborazione di un solo file alla volta richiede molto tempo. Dopo aver eseguito questa app, vedo che la CPU è solo del 10%. Così ho scoperto che posso dividere questi file in 4 batch, mettere ogni batch in una directory e sparare 4, da quattro finestre terminali, quattro processi, quindi ho quattro istanze del mio script, allo stesso tempo, elaborando quelle immagini e il il lavoro richiede 1/4 del tempo.

Il secondo problema è che ho perso tempo dividendo le immagini e i batch e copiando lo script in quattro directory, aprendo 4 finestre terminali, bla bla ...

Come si fa con uno script, senza dover dividere nulla?

Intendo due cose: prima come faccio da uno script bash, eseguo un processo in background? (basta aggiungere & fino alla fine?) Secondo: come posso interrompere l'invio di attività in background dopo aver inviato le quarte attività e mettere lo script in attesa fino alla fine delle attività? Voglio dire, basta inviare una nuova attività in background al termine di una attività, mantenendo sempre 4 attività in parallelo? se non lo faccio, il loop genererà miliardi di attività in background e la CPU si ostruirà.


Risposte:


33

Se ne hai una copia xargsche supporta l'esecuzione parallela con -P, puoi semplicemente farlo

printf '%s\0' *.png | xargs -0 -I {} -P 4 ./pngout -s0 {} R{}

Per altre idee, la wiki di Wooledge Bash ha una sezione nell'articolo sulla gestione dei processi che descrive esattamente ciò che desideri.


2
Ci sono anche "gnu parallel" e "xjobs" progettati per questo caso. È soprattutto una questione di gusti che preferisci.
wnoise

Potresti spiegare il comando proposto? Grazie!
Eugene S

1
@EugeneS Potresti essere un po 'più specifico su quale parte? Printf raccoglie tutti i file png e li passa attraverso una pipe a xargs, che raccoglie argomenti dall'input standard e li combina in argomenti per il pngoutcomando che l'OP ha voluto eseguire. L'opzione chiave è -P 4, che dice a xargs di usare fino a 4 comandi simultanei.
jw013

2
Scusa per non essere preciso. Ero particolarmente interessato perché hai usato la printffunzione qui piuttosto che semplicemente ls .. | grep .. *.png? Inoltre ero interessato ai xargsparametri che hai usato ( -0e -I{}). Grazie!
Eugene S

3
@EugeneS È per la massima correttezza e robustezza. I nomi dei file non sono linee e lsnon possono essere utilizzati per analizzare i nomi dei file in modo portabile e sicuro . Gli unici caratteri sicuri da usare per delimitare i nomi dei file sono \0e /, poiché ogni altro carattere, incluso \n, può far parte del nome del file stesso. Gli printfusi \0per i nomi dei file delimitare, e le -0informa xargsdi questo. Il -I{}dice xargsdi sostituire {}con l'argomento.
jw013,

8

Oltre alle soluzioni già proposte, è possibile creare un makefile che descriva come creare un file compresso da non compresso e utilizzarlo make -j 4per eseguire 4 lavori in parallelo. Il problema è che dovrai nominare i file compressi e non compressi in modo diverso o memorizzarli in directory diverse, altrimenti scrivere una regola di fabbricazione ragionevole sarà impossibile.



5

Per rispondere alle tue due domande:

  • Sì, l'aggiunta e alla fine della riga indica alla shell di avviare un processo in background.
  • usando il waitcomando, puoi chiedere alla shell di attendere il completamento di tutti i processi in background prima di procedere ulteriormente.

Ecco lo script modificato in modo che jvenga utilizzato per tenere traccia del numero di processi in background. Quando NB_CONCURRENT_PROCESSESviene raggiunto, lo script verrà reimpostato jsu 0 e attenderà il completamento di tutti i processi in background prima di riprenderne l'esecuzione.

files=(./*.png)
nb_concurrent_processes=4
j=0
for f in "${files[@]}"
do
        echo "Processing $f file..."
        # take action on each file. $f store current file name
        ./pngout -s0 "$f" R"${f/\.\//}" &
        ((++j == nb_concurrent_processes)) && { j=0; wait; }
done

1
Questo attenderà l'ultimo dei quattro processi simultanei e quindi avvierà un set di altri quattro. Forse si dovrebbe costruire un array di quattro PID e quindi attendere questi PID specifici?
Nils,

Giusto per spiegare le mie correzioni al codice: (1) Per motivi di stile, evita tutti i nomi di variabili maiuscole in quanto potenzialmente in conflitto con le variabili della shell interna. (2) Quotazioni aggiunte per $fecc. (3) Utilizzare [per script compatibili con POSIX, ma per bash puro [[è sempre preferito. In questo caso, ((è più appropriato per l'aritmetica.
jw013
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.