Cztery zadania równolegle… jak to zrobić?

23

Mam kilka obrazów PNG w katalogu. Mam aplikację o nazwie pngout, którą uruchamiam w celu skompresowania tych obrazów. Ta aplikacja jest wywoływana przez skrypt, który zrobiłem. Problem polega na tym, że ten skrypt wykonuje jeden po drugim, mniej więcej tak:

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

Przetwarzanie tylko jednego pliku na raz zajmuje dużo czasu. Po uruchomieniu tej aplikacji widzę, że procesor ma zaledwie 10%. Odkryłem więc, że mogę podzielić te pliki na 4 partie, umieścić każdą partię w katalogu i uruchomić 4, z czterech okien terminala, czterech procesów, więc mam cztery instancje skryptu, jednocześnie przetwarzając te obrazy i praca zajmuje 1/4 czasu.

Drugi problem polega na tym, że straciłem czas na dzielenie obrazów i partii oraz kopiowanie skryptu do czterech katalogów, otwieranie 4 okien terminali, bla bla ...

Jak to zrobić za pomocą jednego skryptu, bez konieczności dzielenia czegokolwiek?

Mam na myśli dwie rzeczy: po pierwsze, jak przejść ze skryptu bash, odpalić proces w tle? (wystarczy dodać & na końcu?) Po drugie: jak przestać wysyłać zadania w tle po wysłaniu czwartych zadań i ustawić skrypt, aby czekał na zakończenie zadań? Mam na myśli, po prostu wysyłając nowe zadanie w tle, gdy kończy się jedno zadanie, utrzymując zawsze 4 zadania równolegle? jeśli tego nie zrobię, pętla uruchomi w tle zilliony zadań i procesor się zatka.

Kosmiczny pies
źródło
Zobacz także Równoległa pętla for
Gilles 'SO- przestań być zły'

Odpowiedzi:

33

Jeśli masz kopię, xargsktóra obsługuje równoległe wykonywanie -P, możesz po prostu to zrobić

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

W przypadku innych pomysłów wiki Wooledge Bash zawiera sekcję w artykule Zarządzanie procesami, dokładnie opisującą to, czego chcesz.

jw013
źródło
2
Istnieją również „równoległe GNU” i „Xjobs” zaprojektowane dla tego przypadku. To w większości kwestia gustu, który wolisz.
wnoise
Czy możesz wyjaśnić proponowane polecenie? Dzięki!
Eugene S
1
@EugeneS Czy mógłbyś być bardziej konkretny w jakiej części? Printf zbiera wszystkie pliki png i przekazuje je przez potok do xargs, który zbiera argumenty ze standardowego wejścia i łączy je w argumenty pngoutpolecenia, które chciał uruchomić OP. Kluczową opcją jest -P 4, która mówi xargsowi, aby używał do 4 równoczesnych poleceń.
jw013
2
Przepraszamy za niedokładność. Byłem szczególnie zainteresowany, dlaczego użyłeś printftu funkcji zamiast zwykłej ls .. | grep .. *.png? Byłem także zainteresowany xargsparametrami, których użyłeś ( -0i -I{}). Dzięki!
Eugene S
3
@EugeneS To dla maksymalnej poprawności i solidności. Nazwy plików nie są wierszami i lsnie można ich używać do parsowania nazw plików przenośnie i bezpiecznie . Jedynymi bezpiecznymi znakami używanymi do rozdzielania nazw plików są \0i /, ponieważ każdy inny znak, łącznie z \n, może być częścią samej nazwy pliku. Do printfzastosowania \0w nazwach plików ograniczają, i -0informuje xargso tym. -I{}Mówi xargszastąpić {}z argumentem.
jw013
8

Oprócz zaproponowanych już rozwiązań możesz utworzyć plik makefile opisujący sposób skompresowania pliku z nieskompresowanego i użyć make -j 4do uruchomienia 4 zadań równolegle. Problem polega na tym, że będziesz musiał inaczej nazwać skompresowane i nieskompresowane pliki lub przechowywać je w różnych katalogach, w przeciwnym razie napisanie rozsądnej reguły make będzie niemożliwe.

9000
źródło
5

Aby odpowiedzieć na dwa pytania:

  • tak, dodanie & na końcu linii poinstruuje powłokę, aby uruchomiła proces w tle.
  • za pomocą waitpolecenia możesz poprosić powłokę, aby poczekała na zakończenie wszystkich procesów w tle, zanim przejdziesz dalej.

Oto zmodyfikowany skrypt, który jsłuży do śledzenia liczby procesów w tle. Kiedy NB_CONCURRENT_PROCESSESzostanie osiągnięty, skrypt zresetuje się jdo 0 i poczeka na zakończenie wszystkich procesów w tle przed wznowieniem jego wykonania.

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
Frederik Deweerdt
źródło
1
Poczeka to na ostatni z czterech współbieżnych procesów, a następnie rozpocznie zestaw kolejnych czterech. Być może należy zbudować tablicę czterech PID, a następnie poczekać na te konkretne PID?
Nils
Aby wyjaśnić moje poprawki do kodu: (1) Ze względu na styl należy unikać wszystkich nazw zmiennych pisanych wielkimi literami, ponieważ mogą one powodować konflikt z wewnętrznymi zmiennymi powłoki. (2) Dodano cytowanie $fitp. (3) Użyj [dla skryptów zgodnych z POSIX, ale [[zawsze preferowany jest czysty bash . W takim przypadku ((jest bardziej odpowiedni dla arytmetyki.
jw013