Uruchamianie poleceń równolegle z ograniczeniem jednoczesnej liczby poleceń

23

Sekwencyjny: for i in {1..1000}; do do_something $i; done- za wolny

Równolegle: for i in {1..1000}; do do_something $i& done- za duże obciążenie

Jak uruchamiać polecenia równolegle, ale nie więcej niż, na przykład, 20 instancji na chwilę?

Teraz zwykle używa hacka for i in {1..1000}; do do_something $i& sleep 5; done, ale nie jest to dobre rozwiązanie.

Aktualizacja 2 : Przekształciła zaakceptowaną odpowiedź w skrypt: http://vi-server.org/vi/parallel

#!/bin/bash

NUM=$1; shift

if [ -z "$NUM" ]; then
    echo "Usage: parallel <number_of_tasks> command"
    echo "    Sets environment variable i from 1 to number_of_tasks"
    echo "    Defaults to 20 processes at a time, use like \"MAKEOPTS='-j5' parallel ...\" to override."
    echo "Example: parallel 100 'echo \$i; sleep \`echo \$RANDOM/6553 | bc -l\`'"
    exit 1
fi

export CMD="$@";

true ${MAKEOPTS:="-j20"}

cat << EOF | make -f - -s $MAKEOPTS
PHONY=jobs
jobs=\$(shell echo {1..$NUM})

all: \${jobs}

\${jobs}:
        i=\$@ sh -c "\$\$CMD"
EOF

Pamiętaj, że musisz zastąpić 8 spacji 2 tabulatorami przed „i =”, aby działało.

Vi.
źródło

Odpowiedzi:

15

GNU Parallel jest do tego stworzony.

seq 1 1000 | parallel -j20 do_something

Może nawet uruchamiać zadania na komputerach zdalnych. Oto przykład ponownego kodowania MP3 do OGG za pomocą server2 i komputera lokalnego z 1 zadaniem na rdzeń procesora:

parallel --trc {.}.ogg -j+0 -S server2,: \
     'mpg321 -w - {} | oggenc -q0 - -o {.}.ogg' ::: *.mp3

Obejrzyj film wprowadzający do GNU Parallel tutaj:

http://www.youtube.com/watch?v=OpaiGYxkSuQ

Ole Tange
źródło
Nie wiem o „moreutils” i że istnieje już narzędzie do tego zadania. Patrzeć i porównywać.
Vi.
1
W parallelmoreutils nie jest GNU Parallel i ma dość ograniczone możliwości. Powyższe polecenie nie będzie działać równolegle z moreutils.
Ole Tange,
1
Jeszcze jedna opcja: xargs --max-procs=20.
Vi.
4

Nie jest to bashowe rozwiązanie, ale powinieneś użyć Makefile, być może z -lnie przekraczającym maksymalnego obciążenia.

NJOBS=1000

.PHONY = jobs
jobs = $(shell echo {1..$(NJOBS)})

all: $(jobs)

$(jobs):
    do_something $@

Następnie rozpocznij 20 zadań jednocześnie

$ make -j20

lub rozpocząć jak najwięcej zadań, nie przekraczając obciążenia 5

$ make -j -l5
Benjamin Bannier
źródło
Na razie wygląda to na niehackerskie rozwiązanie.
Vi.
2
echo -e 'PHONY=jobs\njobs=$(shell echo {1..100000})\n\nall: ${jobs}\n\n${jobs}:\n\t\techo $@; sleep `echo $$RANDOM/6553 | bc -l`' | make -f - -j20Teraz znów wygląda bardziej hacking.
Vi.
@vi: o mój ...
Benjamin Bannier
Przekształciłeś swoje rozwiązanie w skrypt. Teraz można go łatwo używać.
Vi.
2

opublikowanie skryptu w pytaniu z formatowaniem:

#!/bin/bash

NUM=$1; shift

if [ -z "$NUM" ]; then
    echo "Usage: parallel <number_of_tasks> command"
    echo "    Sets environment variable i from 1 to number_of_tasks"
    echo "    Defaults to 20 processes at a time, use like \"MAKEOPTS='-j5' parallel ...\" to override."
    echo "Example: parallel 100 'echo \$i; sleep \`echo \$RANDOM/6553 | bc -l\`'"
    exit 1
fi

export CMD="$@";

true ${MAKEOPTS:="-j20"}

cat << EOF | make -f - -s $MAKEOPTS
PHONY=jobs
jobs=\$(shell echo {1..$NUM})

all: \${jobs}

\${jobs}:
        i=\$@ sh -c "\$\$CMD"
EOF

Pamiętaj, że musisz zastąpić 8 spacji 2 tabulatorami przed „i =”.

warren
źródło
1

Jeden prosty pomysł:

Sprawdź i modulo 20 i wykonaj polecenie shell shell wait przed do_something.

harrymc
źródło
Będzie albo czekał na zakończenie wszystkich bieżących zadań (tworzenie zapadnięć na wykresie liczby zadań), albo czeka na jedno konkretne zadanie, które może zostać zawieszone na dłużej (w tym przypadku ponownie tworzy zapady)
Vi.
@Vi: Shell czeka na wszystkie zadania w tle należące do tej powłoki.
harrymc
1

Możesz użyć, psaby policzyć, ile procesów masz uruchomionych, a gdy tylko spadnie poniżej określonego progu, uruchom inny proces.

Pseudo kod:

i = 1
MAX_PROCESSES=20
NUM_TASKS=1000
do
  get num_processes using ps
  if num_processes < MAX_PROCESSES
    start process $i
    $i = $i + 1
  endif
  sleep 1 # add this to prevent thrashing with ps
until $i > NUM_TASKS
Paul R.
źródło
1
for i in {1..1000}; do 
     (echo $i ; sleep `expr $RANDOM % 5` ) &
     while [ `jobs | wc -l` -ge 20 ] ; do 
         sleep 1 
     done
done
msw
źródło
Może być while [ `jobs | wc -l` -ge 20]; do?
Vi.
jasne, ale w mojej próbce musiałbym wtedy njobsdwukrotnie obliczyć , a wydajność jest dość ważna w skryptach powłoki wykonujących zadania uśpienia;)
msw
Mam na myśli, że twoja wersja nie działa zgodnie z oczekiwaniami. I zmieni sleep 1się sleep 0.1i zacznie średnich njobs do 40-50 zamiast 20. Jeśli istnieje więcej niż 20 miejsc pracy musimy czekać na każda praca zostanie zakończona, a nie tylko czekać na 1 sekundę.
Vi.
0

możesz to zrobić w ten sposób.

threads=20
tempfifo=$PMS_HOME/$$.fifo

trap "exec 1000>&-;exec 1000<&-;exit 0" 2
mkfifo $tempfifo
exec 1000<>$tempfifo
rm -rf $tempfifo

for ((i=1; i<=$threads; i++))
do
    echo >&1000
done

for ((j=1; j<=1000; j++))
do
    read -u1000
    {
        echo $j
        echo >&1000
    } &
done

wait
echo "done!!!!!!!!!!"

używając nazwanych potoków, za każdym razem uruchamia równolegle 20 podpowłok.

Mam nadzieję, że to pomoże :)

ouyangyewei
źródło