GNU parallel: Executando Jobs em Paralelo pelo Shell

GNU parallel: Executando Jobs em Paralelo pelo Shell

O Que Você Vai Aprender

  • Executar muitos comandos simultaneamente entre núcleos de CPU para reduzir o tempo total
  • Evitar a saída intercalada e fora de ordem que você obtém com xargs -P
  • Usar --joblog e --resume para retomar de onde parou, pulando jobs concluídos

Resumo Rápido

  • Muitas tarefas pesadas e independentes (converter, baixar, testar) -> parallel
  • Precisa de saída limpa / ordem de entrada preservada -> -k
  • Quer retomar uma execução interrompida -> --joblog + --resume

Premissas (ambiente alvo)

  • Ubuntu / família Debian (os conceitos se aplicam a outras distros também)
  • Este é o GNU parallel (por Ole Tange). O parallel diferente fornecido em moreutils não é compatível

O que é GNU parallel?

Conclusão: Ele recebe uma lista (de stdin ou argumentos de linha de comando) e executa um comando em cada item em paralelo. Por padrão, executa um job por núcleo de CPU.

GNU parallel pega a ideia do xargs de "construir um comando a partir de uma lista" e a especializa para execução paralela e saída limpa. Duas formas básicas:

# 1) argumentos apos :::
parallel echo ::: a b c

# 2) da entrada padrao
seq 1 3 | parallel echo
a
b
c

Cada um de a, b, c lanca echo como um processo separado ao mesmo tempo. A concorrência padrão é a quantidade de núcleos de CPU, então mais entradas que núcleos são alimentadas nos slots conforme eles ficam livres.

Por padrão, um comando é executado por item de entrada (o oposto do xargs). Para empacotar vários argumentos em uma invocação, use -N (coberto abaixo).

Como instalar e executar o primeiro comando?

Conclusão: Instale com apt install parallel. No primeiro uso, ele imprime um aviso de citação; execute parallel --citation uma vez para registrar seu reconhecimento.

sudo apt update
sudo apt install parallel
parallel --version

GNU parallel pede para ser citado em trabalhos acadêmicos e imprime uma solicitação de citação no primeiro uso. Se isso atrapalha scripts ou CI, execute o seguinte uma vez para registrar o reconhecimento (cria ~/.parallel/will-cite e silencia o aviso).

parallel --citation

Em algumas distros o pacote moreutils fornece um parallel diferente. Sempre verifique se a primeira linha de parallel --version diz GNU parallel. Se não disser, não é a ferramenta GNU.

Por que usar parallel em vez de xargs?

Conclusão: xargs -P também pode executar em paralelo, mas sua saída intercala linha por linha. parallel agrupa a saída por job e pode manter a ordem de entrada com -k.

xargs -P 4 é prático, mas a saída padrão de jobs concorrentes tende a intercalar uma linha por vez. parallel armazena a saída de cada job internamente e a emite como um todo quando o job termina, então nunca mistura.

Aspecto xargs -P parallel
Intercalação de saída Provável Agrupada por job
Saída na ordem de entrada Não garantida Garantida com -k
Poder de placeholders Apenas {} {} {.} {/} etc.
Log de execução / resume Nenhum --joblog --resume
Exibição de progresso Nenhuma --bar --eta

Para velocidade bruta sozinha, xargs -P geralmente é suficiente. parallel ganha seu espaço quando você não pode corromper a saída ou quer retomar.

Como os placeholders funcionam?

Conclusão: {} é a própria entrada. {.} remove a extensão, {/} é o nome base, {//} o diretório, {#} o número do job. Eles são essenciais para construir nomes de saída.

Placeholders dizem ao parallel onde inserir cada entrada. Se você os omitir, um {} é adicionado ao final.

# Converter cada *.wav para um .mp3 de mesmo nome ({.} remove a extensao)
parallel ffmpeg -i {} {.}.mp3 ::: *.wav

Os principais placeholders:

Sintaxe Significado Exemplo (entrada dir/file.txt)
{} A própria entrada dir/file.txt
{.} Extensão removida dir/file
{/} Nome base (diretório removido) file.txt
{//} Parte do diretório dir
{/.} Nome base sem extensão file
{#} Número sequencial do job 1, 2, ...
{%} Número do slot do job 1..(até a concorrência)
# Prefixar cada entrada com seu numero de job
parallel 'echo job {#}: {}' ::: alpha beta gamma
job 1: alpha
job 2: beta
job 3: gamma

Como controlar a quantidade de jobs e a ordem da saída?

Conclusão: Defina a concorrência com -j. -j0 executa o máximo possível, -j 200% é o dobro da quantidade de núcleos. Adicione -k para emitir saída na ordem de entrada.

# 4 jobs por vez
parallel -j 4 ./convert.sh ::: *.dat

# Dobro da quantidade de nucleos (bom para trabalho limitado por I/O)
parallel -j 200% curl -O ::: "${urls[@]}"

# Sem limite de concorrencia (use com cuidado)
parallel -j0 echo ::: {1..100}

Com execução paralela, a saída chega na ordem de conclusão, quebrando o mapeamento entrada-saída. Para emitir na ordem de entrada, adicione -k (--keep-order).

seq 1 5 | parallel -k 'sleep $((RANDOM % 3)); echo {}'
1
2
3
4
5

Antes de ir para produção, adicione --dry-run para imprimir apenas as linhas de comando que o parallel executaria. Detecte erros de expansão de placeholder aqui.

Como combinar múltiplas entradas?

Conclusão: Múltiplos ::: produzem o produto cartesiano. --link emparelha itens por posição. Para linhas multi-coluna, use --colsep e referencie {1} {2}.

# Produto cartesiano: a-1 a-2 b-1 b-2 c-1 c-2 (6 jobs)
parallel echo ::: a b c ::: 1 2
# --link: emparelhar por posicao -> a-1 b-2 c-3
parallel --link echo ::: a b c ::: 1 2 3

Para ler entradas de um arquivo, use :::: (ou -a). Para dividir colunas como um CSV, use --colsep.

# Usar cada linha de hosts.txt como argumento
parallel ping -c1 {} :::: hosts.txt

# Dividir "user,host" em colunas
parallel --colsep ',' ssh {2} -l {1} uptime :::: targets.csv

Como mostrar progresso, log e reexecutar falhas?

Conclusão: --bar mostra uma barra de progresso, --joblog registra o resultado de cada job. Adicione --resume para levar apenas os jobs não concluídos para a próxima execução.

# Mostrar uma barra de progresso
parallel --bar ./task.sh ::: {1..50}

# Registrar um log de execucao (codigo de saida e duracao por job)
parallel --joblog run.log ./task.sh ::: *.dat

Com um --joblog registrado, --resume pula jobs que já tiveram sucesso e continua. Para tentar novamente apenas os que falharam, use --resume-failed.

# Apos uma interrupcao/falha, adicione --resume ao mesmo comando
parallel --joblog run.log --resume ./task.sh ::: *.dat

Para parar cedo em um erro, use --halt.

# Parar apos uma falha, deixando jobs em execucao terminarem
parallel --halt now,fail=1 ./task.sh ::: *.dat

--resume assume o mesmo arquivo --joblog e o mesmo comando. Mudar o comando ou as entradas quebra a retomada correta.

O que é o modo --pipe para dividir stdin?

Conclusão: --pipe divide o próprio fluxo de entrada padrão em blocos e alimenta cada bloco para um comando paralelo. É adequado para agregar logs enormes.

Até agora paralelizamos uma lista de argumentos. --pipe em vez disso divide um único fluxo de entrada para processamento paralelo.

# Dividir um arquivo enorme em blocos de 10MB e grep cada um em paralelo
cat huge.log | parallel --pipe --block 10M grep ERROR

--block define o tamanho de cada bloco. parallel divide nos limites de quebra de linha para que linhas nunca sejam cortadas ao meio.

Receitas práticas

Conclusão: Conversão em massa de imagens, downloads em massa e execução de um comando em muitos hosts são os clássicos. Confirme com --dry-run antes de ir para produção.

Templates para copiar e colar

# Redimensionar imagens em massa (saida nomeada {.}_small.jpg)
parallel convert {} -resize 50% {.}_small.jpg ::: *.jpg

# Baixar uma lista de URLs com 8 jobs
parallel -j8 wget -q ::: $(cat urls.txt)

# Mesmo comando em muitos hosts (saida agrupada por host)
parallel -k --tag ssh {} 'uptime' :::: hosts.txt

# Visualizar primeiro; remova --dry-run quando parecer correto
parallel --dry-run ./batch.sh {} ::: input/*

Adicionar --tag prefixa cada linha de saída com sua entrada (como o nome do host), facilitando identificar qual job produziu qual resultado.

Próximas Leituras