Pergunta

Para maximizar o uso da CPU (eu executar as coisas em um Lenny Debian no EC2) Eu tenho um script simples para trabalhos de lançamento em paralelo:

#!/bin/bash

for i in apache-200901*.log; do echo "Processing $i ..."; do_something_important; done &
for i in apache-200902*.log; do echo "Processing $i ..."; do_something_important; done &
for i in apache-200903*.log; do echo "Processing $i ..."; do_something_important; done &
for i in apache-200904*.log; do echo "Processing $i ..."; do_something_important; done &
...

Estou bastante satisfeito com esta solução de trabalho, no entanto eu não conseguia descobrir como escrever código adicional que só executado uma vez todos os ciclos foram concluídos.

Existe uma maneira de obter o controle dessa?

Foi útil?

Solução

Há uma bash builtin comando para isso.

wait [n ...]
      Wait for each specified process and return its termination  sta‐
      tus.   Each  n  may be a process ID or a job specification; if a
      job spec is given, all processes  in  that  job’s  pipeline  are
      waited  for.  If n is not given, all currently active child pro‐
      cesses are waited for, and the return  status  is  zero.   If  n
      specifies  a  non-existent  process or job, the return status is
      127.  Otherwise, the return status is the  exit  status  of  the
      last process or job waited for.

Outras dicas

Usando GNU Parallel fará seu script ainda mais curto e, possivelmente, mais eficiente:

parallel 'echo "Processing "{}" ..."; do_something_important {}' ::: apache-*.log

Isto irá executar um trabalho por núcleo de CPU e continuar a fazer isso até que todos os arquivos são processados.

A sua solução será, basicamente, dividir os trabalhos em grupos antes de executar. Aqui 32 postos de trabalho em 4 grupos:

agendamento simples

GNU Paralelo vez gera um novo processo quando termina uma - mantendo as CPUs ativo e, portanto, economizando tempo:

GNU programação paralela

Para saber mais:

Eu tive que fazer isso recentemente e acabou com a seguinte solução:

while true; do
  wait -n || {
    code="$?"
    ([[ $code = "127" ]] && exit 0 || exit "$code")
    break
  }
done;

Eis como funciona:

saídas wait -n, logo que um dos (potencialmente muitos) trabalhos de fundo saídas. Ele sempre avalia como true e o ciclo continua até que:

  1. 127 Código de saída: o último trabalho de fundo saído com êxito. Dentro Nesse caso, podemos ignorar o código de saída e sair do sub-shell com código 0.
  2. Qualquer do trabalho em segundo plano falhou. Nós apenas sair do sub-shell com que o código de saída.

Com set -e, isso vai garantir que o script terminará cedo e passar pelo código de saída de qualquer trabalho em segundo plano falhou.

Esta é a minha solução bruta:

function run_task {
        cmd=$1
        output=$2
        concurency=$3
        if [ -f ${output}.done ]; then
                # experiment already run
                echo "Command already run: $cmd. Found output $output"
                return
        fi
        count=`jobs -p | wc -l`
        echo "New active task #$count:  $cmd > $output"
        $cmd > $output && touch $output.done &
        stop=$(($count >= $concurency))
        while [ $stop -eq 1 ]; do
                echo "Waiting for $count worker threads..."
                sleep 1
                count=`jobs -p | wc -l`
                stop=$(($count > $concurency))
        done
}

A idéia é usar "empregos" para ver quantas crianças estão ativos em segundo plano e esperar até este gotas numéricas (uma criança saídas). Uma vez que existe uma criança, a próxima tarefa pode ser iniciado.

Como você pode ver, há também um pouco de lógica extra para evitar correr os mesmos experimentos / comandos várias vezes. Ele faz o trabalho para mim .. No entanto, esta lógica poderia ser ou ignorada ou melhorada (por exemplo, verificação de data e hora de criação de arquivo, parâmetros de entrada, etc.).

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top