0Pricing
DevOps Bootcamp · Lekcja

Orkiestracja obciążeń za pomocą GNU parallel

Rozdzielaj duże zbiory danych wejściowych między rdzenie za pomocą GNU parallel, gniazd zadań i porządkowania wyników.

Orkiestracja obciążeń za pomocą GNU parallel to bezpłatna lekcja DevOps Bootcamp na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej DevOps Bootcamp, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs DevOps Bootcamp zawiera 4 lekcji w sumie.

Czym jest GNU parallel i dlaczego warto go używać

GNU parallel to narzędzie powłoki umożliwiające równoległe uruchamianie zadań na jednym komputerze lub na wielu komputerach. Zamiast przetwarzać dużą listę elementów pojedynczo w pętli for, parallel rozdziela pracę między wszystkie dostępne rdzenie procesora i wykorzystuje je jednocześnie.

  • Szybkość: zadanie trwające sekwencyjnie 8 minut może zakończyć się w około 1 minutę na komputerze z 8 rdzeniami.
  • Prostota: narzędzie przyjmuje dane ze standardowego wejścia, plików lub list argumentów — bez ręcznego zarządzania procesami.
  • Bezpieczeństwo: dane wyjściowe z różnych zadań są rozdzielone; wyniki nigdy nie przeplatają się ze sobą.

Zainstaluj je za pomocą sudo apt install parallel (Debian/Ubuntu) lub brew install parallel (macOS). Wersję sprawdzisz poleceniem parallel --version.

Pierwsze polecenie parallel

Najprostsza forma parallel odczytuje elementy ze standardowego wejścia i uruchamia polecenie dla każdego z nich. Symbol zastępczy {} oznacza bieżący element wejściowy.

Poniższy przykład jednocześnie kompresuje pięć plików dziennika za pomocą gzip. Bez parallel każdy plik byłby kompresowany po kolei. Z jego użyciem jednocześnie można kompresować do N plików, gdzie N oznacza liczbę rdzeni procesora.

#!/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

Sterowanie slotami zadań za pomocą -j

Domyślnie parallel uruchamia jedno zadanie na każdy rdzeń procesora. Możesz zmienić to ustawienie za pomocą flagi -j (lub --jobs).

  • -j 4 — uruchamia dokładnie 4 zadania jednocześnie
  • -j 0 — uruchamia tyle zadań, ile jest danych wejściowych (używaj ostrożnie!)
  • -j 200% — uruchamia dwa razy więcej zadań niż jest rdzeni procesora (przydatne w przypadku zadań zależnych od operacji wejścia-wyjścia)
  • -j 50% — wykorzystuje tylko połowę dostępnych rdzeni

W przypadku zadań obciążających procesor często optymalną wartością jest -j $(nproc). W przypadku zadań związanych z operacjami sieciowymi lub dyskowymi można bezpiecznie przekroczyć liczbę rdzeni, ponieważ procesy większość czasu spędzają na oczekiwaniu.

#!/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."

Odczytywanie danych wejściowych z plików i argumentów

parallel zapewnia elastyczność w wyborze źródła listy danych wejściowych. Nie musisz ograniczać się do przekazywania danych ze standardowego wejścia przez potok.

  • Z pliku: parallel -a urls.txt wget {}
  • Lista argumentów w wierszu poleceń: parallel echo ::: apple banana cherry
  • Wiele źródeł argumentów (iloczyn kartezjański): parallel echo {1}-{2} ::: a b c ::: 1 2 — tworzy a-1, a-2, b-1, b-2, c-1, c-2
  • Jawnie ze standardowego wejścia: cat list.txt | parallel -j4 process {}

Separator ::: informuje parallel, że następujące wartości mają być użyte jako źródło argumentów, a nie odczytywane ze standardowego wejścia.

#!/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

Symbole zastępcze: manipulowanie tokenami wejściowymi

parallel udostępnia kilka podstawień symboli zastępczych, które pozwalają automatycznie wyodrębniać części ciągu wejściowego — jest to szczególnie przydatne w przypadku ścieżek plików.

  • {} — cały element wejściowy
  • {.} — element wejściowy bez rozszerzenia pliku (report.csv → report)
  • {/} — tylko nazwa bazowa (usuwa ścieżkę katalogu)
  • {//} — tylko ścieżka katalogu
  • {/.} — nazwa bazowa bez rozszerzenia

Dzięki temu nie trzeba wywoływać basename / dirname wewnątrz polecenia zadania, co upraszcza i przyspiesza potoki.

#!/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)'

Zachowywanie kolejności danych wyjściowych za pomocą --keep-order

Gdy zadania kończą się w różnym czasie, ich dane wyjściowe stdout pojawiają się w kolejności, w której zadania się kończą. Może to utrudniać odczytywanie dzienników i powodować problemy z analizą danych przez kolejne etapy.

Kolejność danych wyjściowych kontrolują dwie flagi:

  • --keep-order (-k) — wyświetla dane wyjściowe każdego zadania w tej samej kolejności co dane wejściowe, nawet jeśli późniejsze zadanie zakończy się wcześniej. Dane wyjściowe są buforowane do czasu zakończenia wcześniejszych zadań.
  • --line-buffer — rozwiązanie pośrednie: wyświetla kompletne wiersze w miarę ich pojawiania się, bez oczekiwania na zakończenie zadania, ale nigdy nie przeplata częściowo zapisanych wierszy.

Użyj -k, gdy odbiorca kolejnego etapu oczekuje wyników w kolejności danych wejściowych (np. podczas tworzenia posortowanego raportu). Pomiń tę flagę, gdy kolejność nie ma znaczenia i chcesz zobaczyć wyniki jak najszybciej.

#!/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 {}"'

Grupowanie danych wyjściowych w celu uniknięcia przeplatania

Nawet przy uporządkowanych danych wyjściowych, jeśli zadanie wypisuje wiele wierszy, wiersze te mogą przeplatać się z wierszami innego zadania uruchomionego w tym samym czasie. parallel rozwiązuje ten problem automatycznie, buforując kompletny stdout i stderr każdego zadania, a następnie wypisując je jako jeden niepodzielny blok po zakończeniu zadania.

To zachowanie jest domyślnie włączone. Można je wyłączyć za pomocą --ungroup, jeśli potrzebują Państwo strumieniowania danych wyjściowych na żywo (np. w przypadku długo działających zadań z paskami postępu), ale wtedy ponownie możliwe jest przeplatanie danych.

  • Domyślnie: dane wyjściowe są grupowane według zadań — bezpieczne do przetwarzania.
  • --ungroup: dane wyjściowe są przesyłane na żywo — dobre rozwiązanie do interaktywnego monitorowania.
  • --line-buffer: kompromis — wiersze nigdy nie są dzielone, ale zadania mogą przeplatać się na granicach wierszy.

Przekazywanie argumentów wewnątrz funkcji powłoki

Czasami praca, którą chcą Państwo zrównoleglić, obejmuje więcej niż jedno polecenie — jest wieloetapową funkcją powłoki. Funkcję można przekazać do parallel, używając export -f w połączeniu z env_parallel albo wywołując bezpośrednio bash -c.

Najbezpieczniejszym przenośnym podejściem w przypadku złożonych zadań jest wzorzec bash -c '...'. Symbol zastępczy {} jest przekazywany jako $1, gdy polecenie kończy się ciągiem _ {}.

#!/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 {}

Ograniczanie tempa i ponawianie prób za pomocą --delay i --retries

Podczas równoległego korzystania z usług zewnętrznych (API, serwerów zdalnych, baz danych) często potrzebują Państwo ograniczania częstotliwości żądań i odporności na błędy.

  • --delay N — odczekuje N sekund między uruchomieniem kolejnych zadań (dozwolone są wartości ułamkowe, takie jak 0.5). Zapobiega przeciążeniu usługi.
  • --retries N — jeśli zadanie zakończy się kodem różnym od zera, ponawia je maksymalnie N razy przed rezygnacją. Każda próba ponownego wykonania zajmuje nowe miejsce na zadanie.
  • --timeout N — kończy zadanie, jeśli działa ono dłużej niż N sekund. W połączeniu z --retries pozwala sprawnie obsługiwać zawieszone zadania.

Przykład: pobieranie 50 adresów URL przy najwyżej 4 jednoczesnych połączeniach, z opóźnieniem 0,5 s między uruchomieniami oraz 3 ponowieniami w razie niepowodzenia.

#!/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.'

Rozdzielanie pracy między zdalne hosty za pomocą --sshloginfile

parallel może w przejrzysty sposób rozdzielać zadania między zdalne maszyny przez SSH, pełniąc funkcję lekkiego narzędzia do obliczeń klastrowych bez potrzeby instalowania specjalistycznego oprogramowania klastrowego.

  • --sshlogin user@host — uruchamia zadania na określonym zdalnym hoście.
  • --sshloginfile machines.txt — odczytuje listę hostów z pliku (po jednym w wierszu). Użycie : jako specjalnego wpisu pozwala dodatkowo korzystać z maszyny lokalnej.
  • --transfer — kopiuje plik wejściowy na zdalny host przed przetwarzaniem.
  • --return {} — kopiuje plik wynikowy z powrotem po zakończeniu zadania.
  • --cleanup — usuwa przesłane pliki ze zdalnego hosta po ich pobraniu.

Na zdalnym hoście musi być zainstalowany program parallel oraz skonfigurowane uwierzytelnianie SSH za pomocą kluczy (bez monitów o hasło).

Raportowanie postępu i rejestrowanie

W przypadku długo działających obciążeń kluczowe znaczenie ma monitorowanie postępu i diagnozowanie błędów po zakończeniu pracy.

  • --progress — wyświetla na żywo wiersz podsumowania pokazujący liczbę uruchomionych, ukończonych i pozostałych zadań.
  • --eta — szacuje czas do zakończenia na podstawie dotychczasowego średniego czasu trwania zadań.
  • --joblog results.log — zapisuje rozdzielany tabulatorami plik dziennika z jednym wierszem na każde ukończone zadanie, zawierającym kod wyjścia, czas działania i uruchomione polecenie. Jest nieoceniony podczas audytowania błędów.
  • --resume --joblog results.log — pomija zadania, które już występują w pliku dziennika (z kodem wyjścia 0). Jeśli wykonanie partii zostanie przerwane, można je wznowić bez ponownego wykonywania pomyślnie zakończonych zadań.

Połączenie --joblog + --resume jest jedną z najpotężniejszych funkcji GNU parallel, umożliwiających tworzenie niezawodnych potoków produkcyjnych.

#!/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"

Sprawdzenie wiedzy: opcje miejsc na zadania

Sprawdźcie Państwo, jak parallel steruje współbieżnością.

Podsumowanie lekcji: orkiestracja obciążeń za pomocą GNU parallel

Omówili Państwo podstawowy zestaw narzędzi do rozdzielania dużych zbiorów danych wejściowych między rdzenie procesora za pomocą GNU parallel. Oto najważniejsze informacje:

  • Podstawowe użycie: przekierujcie listę do parallel command {} — {} jest zastępowane każdym elementem wejściowym.
  • Miejsca na zadania (-j): precyzyjnie sterują współbieżnością — w przypadku pracy obciążającej procesor należy użyć liczby rdzeni, a w przypadku pracy obciążającej operacje wejścia-wyjścia — wyższych wartości procentowych.
  • Symbole zastępcze ({.}, {/}, {//}, {/.}) pozwalają wygodnie wyodrębniać składowe ścieżek bez dodatkowych poleceń.
  • Kontrola danych wyjściowych: -k zachowuje kolejność danych wejściowych; domyślne grupowanie zapobiega przeplataniu wierszy; --ungroup zapewnia strumieniowanie na żywo.
  • Odporność: --retries, --timeout i --delay zwiększają odporność potoków równoległych na niestabilne zadania i ograniczenia częstotliwości.
  • Możliwość audytu: --joblog rejestruje wynik każdego zadania; --resume pozwala wznowić pracę od miejsca przerwania.
  • Skalowanie horyzontalne: --sshloginfile rozdziela zadania między zdalne maszyny przez SSH bez narzutu typowego dla klastrów.

Opanowanie tych opcji zmienia parallel w gotowy do zastosowań produkcyjnych orkiestrator obciążeń, wbudowany bezpośrednio w powłokę.

Często zadawane pytania

Czy lekcja „Orkiestracja obciążeń za pomocą GNU parallel” jest bezpłatna?

Tak — pełny tekst „Orkiestracja obciążeń za pomocą GNU parallel” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu DevOps Bootcamp, przejdź na CoddyKit PRO. Kurs DevOps Bootcamp zawiera 4 lekcji w sumie.

Co nauczysz się w „Orkiestracja obciążeń za pomocą GNU parallel”?

Rozdzielaj duże zbiory danych wejściowych między rdzenie za pomocą GNU parallel, gniazd zadań i porządkowania wyników. Ćwiczysz DevOps Bootcamp z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć DevOps Bootcamp?

Nie wymagamy żadnego doświadczenia. DevOps Bootcamp w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.

Ile czasu zajmuje lekcja „Orkiestracja obciążeń za pomocą GNU parallel”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji DevOps Bootcamp?

Tak. Każda lekcja DevOps Bootcamp zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Profilowanie skryptów i unikanie zbędnych podpowłok
  2. Równoległość z xargs -P i zadaniami w tle
  3. Orkiestracja obciążeń za pomocą GNU parallel
  4. Potoki strumieniowe i nazwane potoki zapewniające przepustowość
← Powrót do DevOps Bootcamp