Równoległe pętle powłoki

11

Chcę przetwarzać wiele plików, a ponieważ mam tutaj kilka rdzeni, chcę to zrobić równolegle:

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

Znam rozwiązanie Makefile , ale moje polecenia wymagają argumentów z listy globowania powłoki. Znalazłem:

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

Aby go użyć, wystarczy umieścić & po zadaniach i wywołanie oczekujące, parametr podaje liczbę równoległych procesów:

> for i in *; do
>     do_something $i &
>     pwait 10
> done

Ale to nie działa zbyt dobrze, np. Próbowałem z np. Konwersją wielu plików w pętli for, ale dałem mi błąd i pozostawiłem zadania cofnięte.

Nie mogę uwierzyć, że nie zostało to jeszcze zrobione, ponieważ dyskusja na liście mailingowej zsh jest już tak stara. Czy znasz coś lepszego?

matematyka
źródło
Podobne do tego pytania: superuser.com/questions/153630/... Sprawdź, czy ta technika działa dla Ciebie.
JRobert
Przydałoby się opublikowanie komunikatów o błędach.
Wstrzymano do odwołania.
@JRobert tak, wiedziałem o tym, ale tak naprawdę to nie pomaga, ponieważ podejście makefile nie działa, jak powiedziałem! @Dennis: Ok, najpierw pozwalam uruchomić obok obok pokazując mi więcej niż określoną liczbę procesów. Po drugie, nie powraca poprawnie do monitu. Po trzecie, powiedziałem, że pozostawia cofnięte zadania, nie było w porządku: właśnie umieściłem wskaźnik echo "DONE"po pętli, która została wykonana, zanim aktywne zadania nie zostały zakończone. => To sprawiło, że pomyślałem, że prace nie zostały wykonane.
matematyka

Odpowiedzi:

15

Makefile to dobre rozwiązanie twojego problemu. Możesz zaprogramować to równoległe wykonywanie w powłoce, ale jest to trudne, jak zauważyłeś. Równoległa implementacja marki nie tylko zajmie się uruchamianiem zadań i wykrywaniem ich zakończenia, ale także obsługi równoważenia obciążenia, co jest trudne.

Wymóg globowania nie jest przeszkodą: istnieją implementacje, które go obsługują. Marka GNU, która ma rozszerzenie symboli wieloznacznych, takie jak $(wildcard *.c)dostęp do powłoki, np. $(shell mycommand)(Funkcje wyszukiwania w GNU tworzą instrukcję, aby uzyskać więcej informacji). Jest to ustawienie domyślne makew systemie Linux i dostępne w większości innych systemów. Oto szkielet Makefile, który możesz dostosować do swoich potrzeb:

sources = $ (symbol wieloznaczny * .src)

all: $ (źródła: .src = .tgt)

% .tgt: $ .src
    do_something $ <$$ (pochodna_paramy $ <)> $ @

Uruchom coś make -j4w stylu równoległego wykonywania czterech zadań lub make -j -l3utrzymaj średnie obciążenie około 3.

Gilles „SO- przestań być zły”
źródło
8

Nie jestem pewien, jakie są twoje pochodne argumenty. Ale z GNU Parallel http: // www.gnu.org/software/parallel/ możesz to zrobić, aby uruchomić jedno zadanie na rdzeń procesora:

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

Jeśli chcesz uzyskać po prostu zmianę .extension, {.} Może się przydać:

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

Obejrzyj wideo wprowadzające do GNU Parallel na http://www.youtube.com/watch?v=OpaiGYxkSuQ

Ole Tange
źródło
7

Czy użycie polecenia powłoki nie waitdziałałoby dla Ciebie?

for i in *
do
    do_something $i &
done
wait

Pętla wykonuje zadanie, czeka na niego, a następnie wykonuje następne zadanie. Jeśli powyższe nie działa dla Ciebie, twoje może działać lepiej, jeśli się pwaitpóźniej przejdziesz done.

Wstrzymano do odwołania.
źródło
nie, z milionem plików Miałbym milion uruchomionych procesów, czy się mylę?
matematyka
1
@brubelsabs: Cóż, próbowałby wykonać milion procesów. W swoim pytaniu nie powiedziałeś, ile plików potrzebujesz do przetworzenia. Sądzę, że będziesz musiał użyć zagnieżdżonych forpętli, aby ograniczyć to: for file in *; do for i in {1..10}; do do_something "$i" & done; wait; done(niesprawdzone) To powinno zrobić dziesięć na raz i poczekać, aż wszystkie dziesięć z każdej grupy zostanie zakończone przed rozpoczęciem następnej dziesięciu. Twoja pętla wykonuje &dyskusję pojedynczo . Zobacz pytanie, do którego JRobert podłączył inne opcje. Wyszukaj w przepełnieniu stosu inne pytania podobne do twojego (i tego).
Wstrzymano do odwołania.
Jeśli PO przewiduje milion plików, miałby z tym problem for i in *. Musi przekazać argumenty do pętli za pomocą potoku lub czegoś takiego. Następnie zamiast wewnętrznej pętli można uruchomić licznik przyrostowy i uruchomić "micro-"wait"-s"co „$ ((i% 32))” -eq '0'
@DennisWilliamson: połączenie waitz wewnętrzną pętlą licznika działało dla mnie dobrze. Dzięki!
Joel Purra
3

Dlaczego nikt jeszcze nie wspomniał o Xargs?

Zakładając, że masz dokładnie trzy argumenty,

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

W przeciwnym razie użyj ogranicznika (przydatne jest do tego null):

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

EDYCJA: w powyższym przypadku każdy parametr powinien być oddzielony znakiem null, a następnie liczbę parametrów należy określić za pomocą xargs -n.

zebediah49
źródło
Tak, w naszym projekcie ktoś miał ten sam pomysł i działa świetnie nawet pod Windows z MSys.
matematyka
0

Próbowałem niektórych odpowiedzi. Sprawiają, że skrypt jest nieco bardziej skomplikowany niż jest to potrzebne. Najlepiej byłoby użyć parallellub xargsbyłoby lepiej, jednak jeśli operacje wewnątrz pętli for są skomplikowane, może być problematyczne utworzenie dużych i długich plików linii, które będą dostarczane równolegle. zamiast tego możemy użyć źródła w następujący sposób

# 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

Tak wyglądałoby rozwiązanie problemu

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

zdefiniuj zrób coś jako do_something.sh

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

}

wykonać za pomocą xarglubgnu parallel

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

Zakładam, że implikowana jest funkcjonalna niezależność iteracji for.

vegabondx
źródło