Parallel execution: GNU parallel e xargs -P

1. Introdução à Execução Paralela em Shell

Scripts shell frequentemente processam listas de arquivos, URLs ou dados sequencialmente. Quando o número de itens cresce, o tempo de execução escala linearmente — um gargalo para tarefas como compressão, downloads ou processamento de logs. A execução paralela permite rodar múltiplas instâncias de um comando simultaneamente, aproveitando múltiplos núcleos de CPU ou threads de I/O.

Os desafios incluem race conditions (quando múltiplos processos acessam o mesmo recurso), consumo excessivo de memória e saída embaralhada no terminal. Duas ferramentas resolvem esses problemas no shell: xargs -P (nativa do POSIX) e GNU parallel (ferramenta externa robusta). Ambas permitem controlar o número de processos simultâneos, mas com diferentes níveis de sofisticação.

2. Dominando xargs -P para Paralelismo Simples

O xargs tradicional constrói e executa comandos a partir da entrada padrão. A opção -P N especifica quantos processos rodar em paralelo.

Exemplo 1: Compressão paralela de arquivos

# Comprime 20 arquivos .txt usando 4 processos simultâneos
ls *.txt | xargs -P 4 -I {} gzip {}

Exemplo 2: Download em lote de URLs

# Baixa 10 URLs com 3 downloads simultâneos
cat urls.txt | xargs -P 3 -I {} wget -q {}

Exemplo 3: Processamento com limite de argumentos

# Redimensiona imagens com 2 processos, passando 5 argumentos por vez
find . -name "*.jpg" | xargs -P 2 -n 5 convert -resize 800x600

Limitações do xargs -P:
- A saída dos processos é intercalada sem controle de ordem
- Não há indicador de progresso ou logs individuais
- Falta tratamento de erros granular (se um processo falha, o xargs continua)
- Não suporta distribuição remota ou limites de memória

3. GNU Parallel: Instalação e Conceitos Fundamentais

O GNU Parallel é uma ferramenta especializada para execução paralela, disponível na maioria dos gerenciadores de pacotes.

Instalação:

# Debian/Ubuntu
sudo apt install parallel

# macOS
brew install parallel

# RHEL/CentOS
sudo yum install parallel

Estrutura básica:

parallel [opções] comando ::: argumentos

Exemplo 4: Paralelo simples com argumentos inline

# Executa echo com 4 argumentos em paralelo
parallel echo "Processando {}" ::: arquivo1.txt arquivo2.txt arquivo3.txt arquivo4.txt

Modos de entrada:
- Argumentos inline: ::: arg1 arg2 arg3
- Stdin: cat lista.txt | parallel comando
- Arquivo: parallel comando :::: arquivo.txt
- Jobs definidos: parallel ::: "cmd1" "cmd2" "cmd3"

4. Controle de Recursos e Distribuição de Carga

O GNU Parallel oferece controle fino sobre recursos do sistema.

Gerenciamento de CPUs:

# Usa exatamente 4 jobs simultâneos
parallel -j4 gzip ::: *.txt

# Usa todos os núcleos disponíveis (automático)
parallel -j0 gzip ::: *.txt

# Usa 200% (2 jobs por núcleo) para tarefas I/O bound
parallel -j+0 --load 100% gzip ::: *.txt

Limitação de memória:

# Garante pelo menos 500MB livres antes de iniciar novo job
parallel --memfree 500M gzip ::: *.txt

Distribuição remota com --sshlogin:

# Distribui jobs entre servidores remotos
parallel --sshlogin server1,server2,server3 gzip ::: *.txt

Round-robin para balanceamento:

# Distribui argumentos em blocos para cada slot
parallel --round-robin gzip ::: *.txt

5. Manipulação de Saída e Logging em Paralelo

Diferente do xargs, o GNU Parallel preserva a ordem e estrutura da saída.

Preservação de ordem:

# Mantém a ordem dos argumentos na saída
parallel --keep-order echo "{}" ::: a b c d

Buffer por linha:

# Exibe linhas completas sem misturar (útil para comandos com saída longa)
parallel --line-buffer wc -l ::: *.txt

Redirecionamento individual por job:

# Salva stdout e stderr em diretórios separados por argumento
parallel --results logs/ wget {} ::: $(cat urls.txt)
# Gera: logs/1/url1.com/stdout, logs/1/url1.com/stderr

Indicador de progresso:

# Barra de progresso com tempo estimado
parallel --progress wget {} ::: $(cat urls.txt)

6. Tratamento de Erros e Retry em Tarefas Paralelas

O GNU Parallel oferece estratégias robustas para falhas.

Parar em erro:

# Para imediatamente no primeiro erro
parallel --halt-on-error now comando ::: arg1 arg2 arg3

# Para no primeiro erro, mas espera jobs em execução
parallel --halt-on-error soon comando ::: arg1 arg2 arg3

# Nunca para (apenas registra falhas)
parallel --halt-on-error never comando ::: arg1 arg2 arg3

Reexecução automática:

# Tenta até 3 vezes em caso de falha
parallel --retries 3 wget {} ::: $(cat urls.txt)

# Reexecuta apenas jobs que falharam (após execução inicial)
parallel --retry-failed wget {} ::: $(cat urls.txt)

Coleta de erros para depuração:

# Salva stderr de jobs com falha
parallel --joblog log.txt wget {} ::: $(cat urls.txt)
# Analisa falhas depois
grep -E '\t1\t' log.txt | cut -f9

7. Casos de Uso Avançados e Integração com Scripts

Paralelismo misto: xargs + GNU parallel

# Usa xargs para distribuir lotes, GNU parallel dentro de cada lote
find . -name "*.log" | xargs -P 2 -I {} sh -c '
    parallel -j4 gzip ::: $(ls {}/../*.log)
'

Uso com funções Bash complexas:

# Exporta função para ambiente paralelo
minha_funcao() {
    local arquivo="$1"
    processa_complexo "$arquivo" || return 1
}
export -f minha_funcao
parallel --env minha_funcao minha_funcao ::: *.txt

Integração com secret management:

# Evita expor senhas em argumentos
export SENHA=$(vault read -field=password secret/db)
parallel --env SENHA mysql -u user -p"$SENHA" -e "SELECT 1" ::: db1 db2 db3

8. Boas Práticas e Comparação Final

Quando usar xargs -P:
- Tarefas simples sem necessidade de logging
- Scripts portáteis (sem dependências externas)
- Ambientes restritos onde GNU parallel não está disponível

Quando usar GNU parallel:
- Tarefas complexas com tratamento de erros
- Necessidade de progresso e logs estruturados
- Distribuição remota ou limites de recursos
- Preservação de ordem na saída

Dicas de performance:
- Para tarefas CPU-bound, use -j0 (número de núcleos)
- Para tarefas I/O-bound (downloads, leitura de disco), aumente -j para 2-3x núcleos
- Evite overhead excessivo: para comandos muito rápidos, use --xargs ou -X para agrupar argumentos
- Monitore com htop ou vmstat para ajustar concorrência

Checklist de segurança:
- Sempre limite concorrência com -j (nunca deixe ilimitado)
- Use --line-buffer para evitar saída corrompida
- Em scripts críticos, implemente --halt-on-error soon
- Evite compartilhar arquivos de saída entre jobs paralelos
- Teste com --dry-run antes da execução real

Referências