GNU parallel: Executando Jobs em Paralelo pelo Shell

GNU parallel: Executando Jobs em Paralelo pelo Shell

O Que Voce Vai Aprender

  • Executar muitos comandos simultaneamente entre nucleos de CPU para reduzir o tempo total
  • Evitar a saida intercalada e fora de ordem que voce obtem com xargs -P
  • Usar --joblog e --resume para retomar de onde parou, pulando jobs concluidos

Resumo Rapido

  • Muitas tarefas pesadas e independentes (converter, baixar, testar) -> parallel
  • Precisa de saida limpa / ordem de entrada preservada -> -k
  • Quer retomar uma execucao interrompida -> --joblog + --resume

Premissas (ambiente alvo)

  • Ubuntu / familia Debian (os conceitos se aplicam a outras distros tambem)
  • Este e o GNU parallel (por Ole Tange). O parallel diferente fornecido em moreutils nao e compativel

O que e GNU parallel?

Conclusao: Ele recebe uma lista (de stdin ou argumentos de linha de comando) e executa um comando em cada item em paralelo. Por padrao, executa um job por nucleo de CPU.

GNU parallel pega a ideia do xargs de "construir um comando a partir de uma lista" e a especializa para execucao paralela e saida limpa. Duas formas basicas:

# 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 concorrencia padrao e a quantidade de nucleos de CPU, entao mais entradas que nucleos sao alimentadas nos slots conforme eles ficam livres.

Por padrao, um comando e executado por item de entrada (o oposto do xargs). Para empacotar varios argumentos em uma invocacao, use -N (coberto abaixo).

Como instalar e executar o primeiro comando?

Conclusao: Instale com apt install parallel. No primeiro uso, ele imprime um aviso de citacao; 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 academicos e imprime uma solicitacao de citacao 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 nao disser, nao e a ferramenta GNU.

Por que usar parallel em vez de xargs?

Conclusao: xargs -P tambem pode executar em paralelo, mas sua saida intercala linha por linha. parallel agrupa a saida por job e pode manter a ordem de entrada com -k.

xargs -P 4 e pratico, mas a saida padrao de jobs concorrentes tende a intercalar uma linha por vez. parallel armazena a saida de cada job internamente e a emite como um todo quando o job termina, entao nunca mistura.

Aspecto xargs -P parallel
Intercalacao de saida Provavel Agrupada por job
Saida na ordem de entrada Nao garantida Garantida com -k
Poder de placeholders Apenas {} {} {.} {/} etc.
Log de execucao / resume Nenhum --joblog --resume
Exibicao de progresso Nenhuma --bar --eta

Para velocidade bruta sozinha, xargs -P geralmente e suficiente. parallel ganha seu espaco quando voce nao pode corromper a saida ou quer retomar.

Como os placeholders funcionam?

Conclusao: {} e a propria entrada. {.} remove a extensao, {/} e o nome base, {//} o diretorio, {#} o numero do job. Eles sao essenciais para construir nomes de saida.

Placeholders dizem ao parallel onde inserir cada entrada. Se voce os omitir, um {} e 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 propria entrada dir/file.txt
{.} Extensao removida dir/file
{/} Nome base (diretorio removido) file.txt
{//} Parte do diretorio dir
{/.} Nome base sem extensao file
{#} Numero sequencial do job 1, 2, ...
{%} Numero do slot do job 1..(ate a concorrencia)
# 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 saida?

Conclusao: Defina a concorrencia com -j. -j0 executa o maximo possivel, -j 200% e o dobro da quantidade de nucleos. Adicione -k para emitir saida 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 execucao paralela, a saida chega na ordem de conclusao, quebrando o mapeamento entrada-saida. 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 producao, adicione --dry-run para imprimir apenas as linhas de comando que o parallel executaria. Detecte erros de expansao de placeholder aqui.

Como combinar multiplas entradas?

Conclusao: Multiplos ::: produzem o produto cartesiano. --link emparelha itens por posicao. 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?

Conclusao: --bar mostra uma barra de progresso, --joblog registra o resultado de cada job. Adicione --resume para levar apenas os jobs nao concluidos para a proxima execucao.

# 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 ja 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 e o modo --pipe para dividir stdin?

Conclusao: --pipe divide o proprio fluxo de entrada padrao em blocos e alimenta cada bloco para um comando paralelo. E adequado para agregar logs enormes.

Ate agora paralelizamos uma lista de argumentos. --pipe em vez disso divide um unico 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 praticas

Conclusao: Conversao em massa de imagens, downloads em massa e execucao de um comando em muitos hosts sao os classicos. Confirme com --dry-run antes de ir para producao.

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 saida com sua entrada (como o nome do host), facilitando identificar qual job produziu qual resultado.

Proximas Leituras