Powiedzmy, że mam pętlę w Bash:
for foo in `some-command`
do
do-something $foo
done
do-something
jest związany z procesorem i mam ładny błyszczący 4-rdzeniowy procesor. Chciałbym móc biegać do czterech do-something
na raz.
Wydaje się, że naiwne podejście brzmi:
for foo in `some-command`
do
do-something $foo &
done
Spowoduje to uruchomienie wszystkich do-something
s naraz, ale istnieje kilka wad, głównie że zrób coś może mieć również pewne znaczące I / O, które wykonując wszystkie naraz może spowolnić trochę. Innym problemem jest to, że ten blok kodu wraca natychmiast, więc nie ma możliwości wykonania innej pracy po zakończeniu wszystkich do-something
s.
Jak napisałeś tę pętlę, aby zawsze były do-something
uruchomione X- y na raz?
Odpowiedzi:
W zależności od tego, co chcesz zrobić, xargs również może pomóc (tutaj: konwersja dokumentów z pdf2ps):
cpus=$( ls -d /sys/devices/system/cpu/cpu[[:digit:]]* | wc -w ) find . -name \*.pdf | xargs --max-args=1 --max-procs=$cpus pdf2ps
Z dokumentów:
--max-procs=max-procs -P max-procs Run up to max-procs processes at a time; the default is 1. If max-procs is 0, xargs will run as many processes as possible at a time. Use the -n option with -P; otherwise chances are that only one exec will be done.
źródło
find [...] -print0
ixargs -0
.cpus=$(getconf _NPROCESSORS_ONLN)
--max-procs=0
aby uzyskać jak najwięcej procesów?--max-procs=0
bardziej przypomina próbę pytającego (uruchom tyle procesów, ile argumentów).Dzięki GNU Parallel http://www.gnu.org/software/parallel/ możesz pisać:
GNU Parallel obsługuje również uruchamianie zadań na komputerach zdalnych. Spowoduje to uruchomienie jednego na rdzeń procesora na zdalnych komputerach - nawet jeśli mają one inną liczbę rdzeni:
Bardziej zaawansowany przykład: Tutaj podajemy listę plików, na których ma działać my_script. Pliki mają rozszerzenie (może .jpeg). Chcemy, aby dane wyjściowe my_script były umieszczone obok plików w basename.out (np. Foo.jpeg -> foo.out). Chcemy uruchomić my_script raz dla każdego rdzenia komputera i chcemy go również uruchomić na komputerze lokalnym. W przypadku komputerów zdalnych chcemy, aby plik został przetworzony i przesłany na dany komputer. Po zakończeniu działania my_script chcemy przenieść foo.out z powrotem, a następnie usunąć foo.jpeg i foo.out ze zdalnego komputera:
cat list_of_files | \ parallel --trc {.}.out -S server1,server2,: \ "my_script {} > {.}.out"
GNU Parallel zapewnia, że dane wyjściowe z każdego zadania nie są mieszane, więc możesz użyć wyjścia jako wejścia dla innego programu:
Zobacz filmy, aby zobaczyć więcej przykładów: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1
źródło
find
polecenia do generowania listy plików, ponieważ nie tylko zapobiega problemowi, gdy występuje spacja wewnątrz nazwy pliku, który występuje w pliku,for i in ...; do
ale find może również zrobić to,find -name \*.extension1 -or -name \*.extension2
co {.} Radzi sobie bardzo dobrze w GNU równolegle.cat
jest oczywiście bezużyteczny.źródło
Tutaj alternatywne rozwiązanie, które można włożyć do .bashrc i zastosować na co dzień jeden wkład:
function pwait() { while [ $(jobs -p | wc -l) -ge $1 ]; do sleep 1 done }
Aby go użyć, wystarczy umieścić
&
po zadaniach i wywołaniu pwait, parametr podaje liczbę równoległych procesów:for i in *; do do_something $i & pwait 10 done
Byłoby przyjemniejsze w użyciu
wait
zamiast czekania na wyjściejobs -p
, ale nie wydaje się oczywistym rozwiązaniem czekania, aż którekolwiek z podanych zadań zostanie zakończone zamiast wszystkich.źródło
Zamiast zwykłego basha użyj pliku Makefile, a następnie określ liczbę jednoczesnych zadań,
make -jX
gdzie X jest liczbą zadań do jednoczesnego uruchomienia.Możesz też użyć
wait
("man wait
"): uruchom kilka procesów potomnych, wywołajwait
- zakończy się po zakończeniu procesów potomnych.maxjobs = 10 foreach line in `cat file.txt` { jobsrunning = 0 while jobsrunning < maxjobs { do job & jobsrunning += 1 } wait } job ( ){ ... }
Jeśli chcesz zapisać wynik zadania, przypisz ich wynik do zmiennej. Po
wait
sprawdzeniu, co zawiera zmienna.źródło
Może spróbuj narzędzia równoległego zamiast przepisywania pętli? Jestem wielkim fanem xjobs. Cały czas używam xjobs do masowego kopiowania plików w naszej sieci, zwykle podczas konfigurowania nowego serwera bazy danych. http://www.maier-komor.de/xjobs.html
źródło
Jeśli znasz
make
polecenie, przez większość czasu możesz wyrazić listę poleceń, które chcesz uruchomić, jako plik makefile. Na przykład, jeśli chcesz uruchomić $ SOME_COMMAND na plikach * .input, z których każdy daje * .output, możesz użyć makefilea potem po prostu biegnij
aby uruchomić co najwyżej NUMBER poleceń równolegle.
źródło
Chociaż zrobienie tego od razu
bash
jest prawdopodobnie niemożliwe, możesz dość łatwo zrobić półprawo.bstark
dał dobre przybliżenie racji, ale ma następujące wady:Inne przybliżenie, które nie ma tych wad, jest następujące:
scheduleAll() { local job i=0 max=4 pids=() for job; do (( ++i % max == 0 )) && { wait "${pids[@]}" pids=() } bash -c "$job" & pids+=("$!") done wait "${pids[@]}" }
Zwróć uwagę, że ten można łatwo dostosować, aby sprawdzić również kod zakończenia każdego zadania po jego zakończeniu, aby można było ostrzec użytkownika, jeśli zadanie się nie powiedzie, lub ustawić kod zakończenia w
scheduleAll
zależności od liczby zadań, które zakończyły się niepowodzeniem, lub coś innego.Problem z tym kodem jest taki, że:
Rozwiązanie, które rozwiązuje ten ostatni problem, musiałoby użyć
kill -0
do sondowania, czy któryś z procesów zniknął, zamiastwait
i zaplanowania następnego zadania. Jednak wprowadza to mały nowy problem: między zakończeniem zadania akill -0
sprawdzeniem, czy się skończył , występuje wyścig . Jeśli zadanie się zakończyło, a inny proces w systemie zostanie uruchomiony w tym samym czasie, biorąc losowy PID, który jest taki sam, jak zadanie, które właśnie się zakończyło,kill -0
nie zauważą, że praca została zakończona i wszystko znowu się zepsuje.Idealne rozwiązanie nie jest możliwe w
bash
.źródło
funkcja dla bash:
parallel () { awk "BEGIN{print \"all: ALL_TARGETS\\n\"}{print \"TARGET_\"NR\":\\n\\t@-\"\$0\"\\n\"}END{printf \"ALL_TARGETS:\";for(i=1;i<=NR;i++){printf \" TARGET_%d\",i};print\"\\n\"}" | make $@ -f - all }
za pomocą:
źródło
make -j
jest sprytne, ale bez żadnego wyjaśnienia i tej plamy kodu Awk tylko do zapisu, powstrzymuję się od głosowania za.Projekt, nad którym pracuję, używa polecenia wait do kontrolowania równoległych procesów powłoki (właściwie ksh). Aby rozwiać obawy dotyczące operacji we / wy, w nowoczesnym systemie operacyjnym możliwe jest, że wykonywanie równoległe faktycznie zwiększy wydajność. Jeśli wszystkie procesy odczytują te same bloki na dysku, tylko pierwszy proces będzie musiał trafić do fizycznego sprzętu. Inne procesy często będą w stanie pobrać blok z pamięci podręcznej dysku systemu operacyjnego w pamięci. Oczywiście odczyt z pamięci jest o kilka rzędów wielkości szybszy niż odczyt z dysku. Ponadto korzyść nie wymaga żadnych zmian w kodowaniu.
źródło
Może to być wystarczające do większości zastosowań, ale nie jest optymalne.
#!/bin/bash n=0 maxjobs=10 for i in *.m4a ; do # ( DO SOMETHING ) & # limit jobs if (( $(($((++n)) % $maxjobs)) == 0 )) ; then wait # wait until all have finished (not optimal, but most times good enough) echo $n wait fi done
źródło
Oto jak udało mi się rozwiązać ten problem w skrypcie bash:
#! /bin/bash MAX_JOBS=32 FILE_LIST=($(cat ${1})) echo Length ${#FILE_LIST[@]} for ((INDEX=0; INDEX < ${#FILE_LIST[@]}; INDEX=$((${INDEX}+${MAX_JOBS})) )); do JOBS_RUNNING=0 while ((JOBS_RUNNING < MAX_JOBS)) do I=$((${INDEX}+${JOBS_RUNNING})) FILE=${FILE_LIST[${I}]} if [ "$FILE" != "" ];then echo $JOBS_RUNNING $FILE ./M22Checker ${FILE} & else echo $JOBS_RUNNING NULL & fi JOBS_RUNNING=$((JOBS_RUNNING+1)) done wait done
źródło
Naprawdę późno na imprezę tutaj, ale oto inne rozwiązanie.
Wiele rozwiązań nie obsługuje spacji / znaków specjalnych w poleceniach, nie powoduje ciągłego działania N zadań, zjada procesora w zajętych pętlach lub polega na zewnętrznych zależnościach (np. GNU
parallel
).Z inspiracji do obsługi procesów martwych / zombie , oto czyste rozwiązanie bash:
function run_parallel_jobs { local concurrent_max=$1 local callback=$2 local cmds=("${@:3}") local jobs=( ) while [[ "${#cmds[@]}" -gt 0 ]] || [[ "${#jobs[@]}" -gt 0 ]]; do while [[ "${#jobs[@]}" -lt $concurrent_max ]] && [[ "${#cmds[@]}" -gt 0 ]]; do local cmd="${cmds[0]}" cmds=("${cmds[@]:1}") bash -c "$cmd" & jobs+=($!) done local job="${jobs[0]}" jobs=("${jobs[@]:1}") local state="$(ps -p $job -o state= 2>/dev/null)" if [[ "$state" == "D" ]] || [[ "$state" == "Z" ]]; then $callback $job else wait $job $callback $job $? fi done }
I przykładowe użycie:
function job_done { if [[ $# -lt 2 ]]; then echo "PID $1 died unexpectedly" else echo "PID $1 exited $2" fi } cmds=( \ "echo 1; sleep 1; exit 1" \ "echo 2; sleep 2; exit 2" \ "echo 3; sleep 3; exit 3" \ "echo 4; sleep 4; exit 4" \ "echo 5; sleep 5; exit 5" \ ) # cpus="$(getconf _NPROCESSORS_ONLN)" cpus=3 run_parallel_jobs $cpus "job_done" "${cmds[@]}"
Wyjście:
Do obsługi danych wyjściowych dla poszczególnych procesów
$$
można użyć do zalogowania się do pliku, na przykład:function job_done { cat "$1.log" } cmds=( \ "echo 1 \$\$ >\$\$.log" \ "echo 2 \$\$ >\$\$.log" \ ) run_parallel_jobs 2 "job_done" "${cmds[@]}"
Wynik:
źródło
Możesz użyć prostej zagnieżdżonej pętli for (podstaw poniżej odpowiednie liczby całkowite dla N i M):
for i in {1..N}; do (for j in {1..M}; do do_something; done & ); done
Spowoduje to wykonanie do_something N * M razy w M rundach, przy czym każda runda wykonuje równolegle N zadań. Możesz ustawić N równe liczbie posiadanych procesorów.
źródło
Moje rozwiązanie, aby zawsze utrzymywać określoną liczbę procesów, śledzić błędy i obsługiwać procesy ubnterruptible / zombie:
function log { echo "$1" } # Take a list of commands to run, runs them sequentially with numberOfProcesses commands simultaneously runs # Returns the number of non zero exit codes from commands function ParallelExec { local numberOfProcesses="${1}" # Number of simultaneous commands to run local commandsArg="${2}" # Semi-colon separated list of commands local pid local runningPids=0 local counter=0 local commandsArray local pidsArray local newPidsArray local retval local retvalAll=0 local pidState local commandsArrayPid IFS=';' read -r -a commandsArray <<< "$commandsArg" log "Runnning ${#commandsArray[@]} commands in $numberOfProcesses simultaneous processes." while [ $counter -lt "${#commandsArray[@]}" ] || [ ${#pidsArray[@]} -gt 0 ]; do while [ $counter -lt "${#commandsArray[@]}" ] && [ ${#pidsArray[@]} -lt $numberOfProcesses ]; do log "Running command [${commandsArray[$counter]}]." eval "${commandsArray[$counter]}" & pid=$! pidsArray+=($pid) commandsArrayPid[$pid]="${commandsArray[$counter]}" counter=$((counter+1)) done newPidsArray=() for pid in "${pidsArray[@]}"; do # Handle uninterruptible sleep state or zombies by ommiting them from running process array (How to kill that is already dead ? :) if kill -0 $pid > /dev/null 2>&1; then pidState=$(ps -p$pid -o state= 2 > /dev/null) if [ "$pidState" != "D" ] && [ "$pidState" != "Z" ]; then newPidsArray+=($pid) fi else # pid is dead, get it's exit code from wait command wait $pid retval=$? if [ $retval -ne 0 ]; then log "Command [${commandsArrayPid[$pid]}] failed with exit code [$retval]." retvalAll=$((retvalAll+1)) fi fi done pidsArray=("${newPidsArray[@]}") # Add a trivial sleep time so bash won't eat all CPU sleep .05 done return $retvalAll }
Stosowanie:
cmds="du -csh /var;du -csh /tmp;sleep 3;du -csh /root;sleep 10; du -csh /home" # Execute 2 processes at a time ParallelExec 2 "$cmds" # Execute 4 processes at a time ParallelExec 4 "$cmds"
źródło
$ DOMAINS = "lista niektórych domen w poleceniach" dla foo in
some-command
doeval `some-command for $DOMAINS` & job[$i]=$! i=$(( i + 1))
Gotowe
Ndomeny =
echo $DOMAINS |wc -w
for i w $ (seq 1 1 $ Ndomains) wykonaj echo "czekaj na $ {praca [$ i]}" czekaj "$ {praca [$ i]}" gotowe
w tej koncepcji będzie działać dla równoległości. Ważną rzeczą jest to, że ostatnia linia eval to „&”, co spowoduje umieszczenie poleceń w tle.
źródło