0Pricing
DevOps Bootcamp · Lezione

Orchestrare carichi di lavoro con GNU parallel

Distribuisca grandi insiemi di input tra i core con GNU parallel, slot di esecuzione e ordinamento dei risultati.

Orchestrare carichi di lavoro con GNU parallel è una lezione DevOps Bootcamp gratuita su CoddyKit. Questa è la lezione 3 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento DevOps Bootcamp, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso DevOps Bootcamp include 4 lezioni in totale.

Che cos'è GNU parallel e perché utilizzarlo?

GNU parallel è uno strumento shell che consente di eseguire processi in parallelo su una o più macchine. Invece di elaborare un lungo elenco di elementi uno alla volta in un ciclo for, parallel distribuisce il lavoro su tutti i core della CPU disponibili simultaneamente.

  • Velocità: un'attività che richiede 8 minuti in sequenza può terminare in circa 1 minuto su una macchina con 8 core.
  • Semplicità: accetta input da stdin, da file o da elenchi di argomenti, senza gestione manuale dei processi.
  • Sicurezza: l'output dei diversi processi resta separato; i risultati non vengono mai intercalati.

Lo installi con sudo apt install parallel (Debian/Ubuntu) o brew install parallel (macOS). Verifichi l'installazione con parallel --version.

Il primo comando parallel

La forma più semplice di parallel legge gli elementi da stdin ed esegue un comando per ciascuno di essi. Il segnaposto {} rappresenta l'elemento di input corrente.

L'esempio seguente comprime contemporaneamente cinque file di log utilizzando gzip. Senza parallel, ogni file verrebbe compresso uno dopo l'altro. Con parallel, vengono compressi contemporaneamente fino a N file, dove N è il numero di core della CPU.

#!/usr/bin/env bash
# Create sample files first
for i in 1 2 3 4 5; do
  dd if=/dev/urandom bs=1M count=2 of="log_${i}.txt" 2>/dev/null
done

# Compress all of them in parallel
ls log_*.txt | parallel gzip {}

echo "Done. Compressed files:"
ls log_*.txt.gz

Controllare gli slot dei processi con -j

Per impostazione predefinita, parallel esegue un processo per ogni core della CPU. Può modificare questo comportamento con l'opzione -j (o --jobs).

  • -j 4 — esegue esattamente 4 processi contemporaneamente
  • -j 0 — esegue tanti processi quanti sono gli input (utilizzi questa opzione con cautela!)
  • -j 200% — esegue un numero di processi doppio rispetto ai core della CPU (utile per i carichi vincolati dall'I/O)
  • -j 50% — utilizza solo metà dei core disponibili

Per le attività vincolate dalla CPU, -j $(nproc) è spesso la scelta ottimale. Per le attività di rete o di I/O su disco, può superare tranquillamente il numero di core, perché i processi trascorrono la maggior parte del tempo in attesa.

#!/usr/bin/env bash
# Show how many cores are available
echo "CPU cores: $(nproc)"

# Run 8 sleep jobs but limit to 3 at a time
# -j 3 means at most 3 jobs run simultaneously
seq 1 8 | parallel -j 3 'echo "Starting job {}"; sleep 1; echo "Done job {}"'

echo "All jobs finished."

Leggere l'input da file e argomenti

parallel è flessibile riguardo alla fonte dell'elenco di input. Non è limitato all'uso di una pipe da stdin.

  • Da un file: parallel -a urls.txt wget {}
  • Elenco di argomenti inline: parallel echo ::: apple banana cherry
  • Più fonti di argomenti (prodotto cartesiano): parallel echo {1}-{2} ::: a b c ::: 1 2 — produce a-1, a-2, b-1, b-2, c-1, c-2
  • Esplicitamente da stdin: cat list.txt | parallel -j4 process {}

Il separatore ::: indica a parallel di utilizzare i valori seguenti come fonte di argomenti invece di leggere da stdin.

#!/usr/bin/env bash
# Inline list with :::
parallel echo 'Hello from {}' ::: Alice Bob Carol Dave

echo '---'

# Cartesian product: combine two lists
# Generates: dev-v1, dev-v2, prod-v1, prod-v2
parallel echo 'Deploy {1} to env {2}' ::: v1 v2 ::: dev prod

Segnaposto: manipolare i token di input

parallel offre diversi segnaposto per le sostituzioni, che consentono di estrarre automaticamente parti della stringa di input, una funzione molto utile quando gli input sono percorsi di file.

  • {} — l'elemento di input completo
  • {.} — l'input senza estensione (report.csv → report)
  • {/} — solo il nome di base (rimuove il percorso della directory)
  • {//} — solo il percorso della directory
  • {/.} — nome di base senza estensione

Questi segnaposto eliminano la necessità di chiamare basename / dirname all'interno del comando del processo, rendendo le pipeline più pulite e veloci.

#!/usr/bin/env bash
# Demonstrate placeholder substitutions
parallel --dry-run 'convert {} -resize 800x600 {.}_thumb.jpg' \
  ::: /photos/vacation/beach.png /photos/work/team.png

# {.}  strips extension: /photos/vacation/beach
# Result command shown (--dry-run does NOT execute):
#  convert /photos/vacation/beach.png -resize 800x600 /photos/vacation/beach_thumb.jpg
#  convert /photos/work/team.png      -resize 800x600 /photos/work/team_thumb.jpg
echo 'No files were changed (dry run)'

Mantenere l'ordine dell'output con --keep-order

Quando i processi terminano in momenti diversi, il loro output stdout viene visualizzato nell'ordine in cui terminano. Ciò può rendere difficili da leggere i log e inaffidabile l'analisi successiva.

Due opzioni controllano l'ordine dell'output:

  • --keep-order (-k) — stampa l'output di ogni processo nello stesso ordine dell'input, anche se un processo successivo termina prima. L'output viene memorizzato in un buffer finché i processi precedenti non terminano.
  • --line-buffer — una soluzione intermedia: emette le righe complete man mano che arrivano, senza attendere il completamento del processo, ma non interlaccia mai righe scritte solo parzialmente.

Utilizzi -k quando il consumer a valle si aspetta i risultati nell'ordine dell'input, ad esempio per creare un report ordinato. Lo ometta quando l'ordine non è importante e desidera visualizzare i risultati il prima possibile.

#!/usr/bin/env bash
# Without -k: output order is unpredictable
echo '--- Without --keep-order ---'
seq 5 1 1 | parallel 'sleep 0.$((RANDOM % 5)); echo "Result for {}"'

echo

# With -k: output always appears as 5, 4, 3, 2, 1
echo '--- With --keep-order (-k) ---'
seq 5 1 1 | parallel -k 'sleep 0.$((RANDOM % 5)); echo "Result for {}"'

Raggruppare l’output per evitare l’interleaving

Anche con l’output ordinato, se un job stampa più righe, queste possono interlevarsi con le righe di un altro job in esecuzione nello stesso momento. parallel risolve automaticamente il problema memorizzando temporaneamente l’intero stdout e stderr di ogni job, quindi stampandoli come un unico blocco atomico al termine del job.

Questo comportamento è attivo per impostazione predefinita. Può essere disabilitato con --ungroup se è necessario trasmettere l’output in tempo reale, ad esempio per job di lunga durata con barre di avanzamento; in tal caso, però, l’interleaving diventa nuovamente possibile.

  • Predefinito: l’output viene raggruppato per job — sicuro da analizzare.
  • --ungroup: l’output viene trasmesso in tempo reale — ideale per il monitoraggio interattivo.
  • --line-buffer: una soluzione intermedia — le righe non vengono mai suddivise, ma i job possono interlevarsi ai confini tra le righe.

Passare argomenti all’interno delle funzioni della shell

A volte il lavoro da eseguire in parallelo è più complesso di un singolo comando: si tratta di una funzione della shell composta da più passaggi. È possibile passare una funzione a parallel usando export -f insieme a env_parallel, oppure chiamando direttamente bash -c.

L’approccio portabile più sicuro per i job complessi è il modello bash -c '...'. Il segnaposto {} viene passato come $1 quando il comando termina con _ {}.

#!/usr/bin/env bash
# Define a multi-step processing function
process_item() {
  local item="$1"
  echo "[START] $item"
  # Simulate two steps
  sleep 0.2
  local result=$(echo "$item" | tr '[:lower:]' '[:upper:]')
  echo "[END]   $item -> $result"
}

export -f process_item

# Run the function in parallel for each input
echo 'alpha beta gamma delta epsilon' | tr ' ' '\n' \
  | parallel -j 3 process_item {}

Limitare la frequenza e riprovare con --delay e --retries

Quando si accede in parallelo a servizi esterni, come API, server remoti o database, spesso sono necessari il controllo della frequenza e la tolleranza agli errori.

  • --delay N — attende N secondi tra l’avvio di un nuovo job e quello successivo; sono ammessi valori frazionari come 0.5. Impedisce di sovraccaricare un servizio.
  • --retries N — se un job termina con uno stato diverso da zero, lo riprova fino a N volte prima di arrendersi. Ogni tentativo conta come un nuovo slot di job.
  • --timeout N — interrompe un job se dura più di N secondi. In combinazione con --retries, gestisce correttamente i job bloccati.

Esempio: scaricare 50 URL con al massimo 4 connessioni simultanee, un ritardo di avvio di 0.5 s tra un job e l’altro e 3 tentativi in caso di errore.

#!/usr/bin/env bash
# Simulate downloading URLs with throttling and retries
# (using echo instead of curl so this is self-contained)

download_url() {
  local url="$1"
  # Randomly fail ~30% of the time to demo --retries
  if (( RANDOM % 10 < 3 )); then
    echo "FAIL: $url" >&2
    return 1
  fi
  echo "OK:   $url downloaded"
}

export -f download_url

printf 'https://example.com/file%d\n' $(seq 1 10) \
  | parallel -j 4 --delay 0.2 --retries 3 download_url {}

echo 'All downloads attempted.'

Distribuire il lavoro su host remoti con --sshloginfile

parallel può distribuire in modo trasparente i job su macchine remote tramite SSH, diventando uno strumento leggero per il calcolo su cluster senza richiedere software specifici per cluster.

  • --sshlogin user@host — esegue i job su uno specifico host remoto.
  • --sshloginfile machines.txt — legge un elenco di host da un file, uno per riga. Usare : come voce speciale per includere anche la macchina locale.
  • --transfer — copia il file di input sull’host remoto prima dell’elaborazione.
  • --return {} — ricopia il file risultante al termine del job.
  • --cleanup — elimina i file trasferiti dall’host remoto dopo il recupero.

L’host remoto deve avere parallel installato e l’autenticazione basata su chiavi SSH configurata, senza richieste di password.

Monitoraggio dell’avanzamento e registrazione dei log

Per i carichi di lavoro di lunga durata è essenziale monitorare l’avanzamento e diagnosticare gli errori a posteriori.

  • --progress — stampa una riga di riepilogo aggiornata in tempo reale, che mostra quanti job sono in esecuzione, completati e ancora da eseguire.
  • --eta — stima il tempo rimanente in base alla durata media dei job eseguiti finora.
  • --joblog results.log — scrive un file di log separato da tabulazioni, con una riga per ogni job completato, includendo codice di uscita, durata e comando eseguito. È indispensabile per verificare gli errori.
  • --resume --joblog results.log — salta i job già presenti nel file di log con codice di uscita 0. Se un’esecuzione batch viene interrotta, è possibile riprenderla senza ripetere il lavoro completato correttamente.

La combinazione di --joblog e --resume è una delle funzionalità più potenti di GNU parallel per creare pipeline di produzione affidabili.

#!/usr/bin/env bash
LOGFILE="/tmp/parallel_demo_$$.log"

# Run jobs and record results to a log
seq 1 12 | parallel \
  --jobs 4 \
  --progress \
  --joblog "$LOGFILE" \
  'sleep 0.1; echo "Processed item {}"'

echo
echo '=== Job Log (first 5 entries) ==='
head -6 "$LOGFILE"

# Show only failed jobs (exit value != 0)
echo '=== Failed jobs ==='
awk 'NR>1 && $7 != 0 { print $0 }' "$LOGFILE" || echo '(none)'

rm -f "$LOGFILE"

Verifica delle conoscenze: flag degli slot di job

Verifichi la propria comprensione del modo in cui parallel controlla la concorrenza.

Riepilogo della lezione: orchestrare i carichi di lavoro con GNU parallel

Ha esaminato gli strumenti fondamentali per distribuire grandi insiemi di input tra i core della CPU con GNU parallel. Ecco i concetti principali da ricordare:

  • Utilizzo di base: convogliare un elenco in parallel command {} — {} viene sostituito da ogni elemento dell’input.
  • Slot di job (-j): controllano con precisione la concorrenza — usare il numero di core per i carichi vincolati dalla CPU e percentuali più elevate per quelli vincolati dall’I/O.
  • Segnaposto ({.}, {/}, {//}, {/.}) estraggono in modo pulito i componenti dei percorsi senza comandi aggiuntivi.
  • Controllo dell’output: -k conserva l’ordine dell’input; il raggruppamento predefinito impedisce l’interleaving delle righe; --ungroup consente la trasmissione in tempo reale.
  • Resilienza: --retries, --timeout e --delay rendono le pipeline parallele robuste nei confronti di job instabili e dei limiti di frequenza.
  • Verificabilità: --joblog registra l’esito di ogni job; --resume consente di riprendere dal punto dell’interruzione.
  • Scalabilità orizzontale: --sshloginfile distribuisce i job su macchine remote tramite SSH senza il sovraccarico di un cluster.

Imparare a usare queste opzioni trasforma parallel in un orchestratore di carichi di lavoro adatto alla produzione, integrato direttamente nella shell.

Domande Frequenti

La lezione «Orchestrare carichi di lavoro con GNU parallel» è gratuita?

Sì — il testo completo di «Orchestrare carichi di lavoro con GNU parallel» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso DevOps Bootcamp, passa a CoddyKit PRO. Il corso DevOps Bootcamp include 4 lezioni in totale.

Cosa imparerò in «Orchestrare carichi di lavoro con GNU parallel»?

Distribuisca grandi insiemi di input tra i core con GNU parallel, slot di esecuzione e ordinamento dei risultati. Eserciti DevOps Bootcamp con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare DevOps Bootcamp?

Non è richiesta alcuna esperienza precedente. DevOps Bootcamp su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 3 di 4.

Quanto tempo richiede la lezione «Orchestrare carichi di lavoro con GNU parallel»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione DevOps Bootcamp?

Sì. Ogni lezione DevOps Bootcamp include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Profilare gli script ed evitare subshell inutili
  2. Parallelismo con xargs -P e processi in background
  3. Orchestrare carichi di lavoro con GNU parallel
  4. Pipeline di streaming e named pipe per le prestazioni
← Torna a DevOps Bootcamp