Executando comandos em paralelo com um limite de número simultâneo de comandos

23

Sequencial: for i in {1..1000}; do do_something $i; done- muito lento

Paralelo: for i in {1..1000}; do do_something $i& done- muita carga

Como executar comandos em paralelo, mas não mais do que, por exemplo, 20 instâncias por momento?

Agora geralmente usando o hack like for i in {1..1000}; do do_something $i& sleep 5; done, mas essa não é uma boa solução.

Atualização 2 : converteu a resposta aceita em um script: 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

Observe que você deve substituir 8 espaços por 2 guias antes de "i =" para fazê-lo funcionar.

Vi.
fonte

Respostas:

15

Paralelo GNU é feito para isso.

seq 1 1000 | parallel -j20 do_something

Pode até executar trabalhos em computadores remotos. Aqui está um exemplo para recodificar um MP3 para OGG usando o server2 e o computador local executando 1 trabalho por núcleo de CPU:

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

Assista a um vídeo de introdução ao GNU Parallel aqui:

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

Ole Tange
fonte
Ainda não sei sobre "moreutils" e que já existe uma ferramenta para o trabalho. Olhando e comparando.
Vi.
1
O parallelin moreutils não é GNU Parallel e é bastante limitado em suas opções. O comando acima não será executado com o paralelo de moreutils.
precisa saber é o seguinte
1
Mais uma opção: xargs --max-procs=20.
Vi.
4

Não é uma solução bash, mas você deve usar um Makefile, possivelmente -lpara não exceder uma carga máxima.

NJOBS=1000

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

all: $(jobs)

$(jobs):
    do_something $@

Então, para iniciar 20 trabalhos por vez, faça

$ make -j20

ou iniciar o maior número possível de tarefas sem exceder uma carga de 5

$ make -j -l5
Benjamin Bannier
fonte
Parece que a solução não hacky por enquanto.
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 - -j20Agora parece mais hacky novamente.
Vi.
@vi: oh meu ....
Benjamin Bannier
Convertido sua solução em um script. Agora pode ser usado com facilidade.
Vi.
2

postando o script na pergunta com formatação:

#!/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

Observe que você deve substituir 8 espaços por 2 guias antes de "i =".

warren
fonte
1

Uma ideia simples:

Verifique o módulo 20 e execute o comando shell de espera antes de fazer alguma coisa.

harrymc
fonte
Ele aguardará a conclusão de todas as tarefas atuais (criando afundamentos no gráfico de número de tarefas) ou aguardará uma tarefa específica que possa parar por mais tempo (novamente criando afundamentos nesse caso)
Vi.
@ Vi: Shell wait é para todas as tarefas em segundo plano que pertencem a esse shell.
harrymc
1

Você pode usar pspara contar quantos processos você está executando e, sempre que isso cai abaixo de um determinado limite, você inicia outro processo.

Pseudo-código:

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
fonte
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
fonte
Pode ser while [ `jobs | wc -l` -ge 20]; do?
Vi.
certo, mas na minha amostra, eu então tem que calcular njobsduas vezes, eo desempenho é muito importante em scripts shell que as tarefas de execução do sono;)
msw
Quero dizer, sua versão não funciona conforme o esperado. Eu mudo sleep 1para sleep 0.1e ele começa a média de njobs para 40-50 em vez de 20. Se houver mais de 20 trabalhos, precisamos esperar que qualquer trabalho seja concluído, e não apenas esperar 1 segundo.
Vi.
0

você pode fazer assim.

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!!!!!!!!!!"

usando pipes nomeados, sempre executa 20 subcascas em paralelo.

Espero que ajude :)

ouyangyewei
fonte