0Pricing
Linux Command Line & Bash Scripting Mastery · Lekcja

Potoki strumieniowe i nazwane potoki zapewniające przepustowość

Używaj FIFO i podstawiania procesów do przesyłania danych między etapami bez plików pośrednich.

Potoki strumieniowe i nazwane potoki zapewniające przepustowość to bezpłatna lekcja Linux Command Line & Bash Scripting Mastery na CoddyKit. To lekcja 4 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 Linux Command Line & Bash Scripting Mastery, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Linux Command Line & Bash Scripting Mastery zawiera 4 lekcji w sumie.

Dlaczego pliki pośrednie obniżają przepustowość

Gdy łączą Państwo polecenia w sposób sort file.txt > tmp.txt && uniq tmp.txt > result.txt, ponoszą Państwo ukryty koszt: zapisów na dysku, odczytów z dysku oraz zatrzymania potoku do czasu całkowitego zakończenia pierwszego etapu przed rozpoczęciem kolejnego.

Potoki strumieniowe eliminują ten koszt. Dane przepływają bezpośrednio od producenta do odbiorcy w pamięci, etap po etapie, współbieżnie. To podstawowa idea potoków systemu Unix — a nazwane potoki (FIFO) rozwijają ją jeszcze dalej.

  • Potok anonimowy (|): łączy dwa sąsiednie polecenia w tym samym wierszu powłoki.
  • Potok nazwany (FIFO): specjalny plik w systemie plików, który pozwala niezależnym procesom przesyłać dane strumieniowo.
  • Podstawianie procesów: pozwala traktować dane wyjściowe innego polecenia tak, jakby były plikiem.

W tej lekcji pokażemy, jak zastosować wszystkie trzy mechanizmy, aby zmaksymalizować przepustowość w rzeczywistych procesach Bash.

Budowa potoku strumieniowego

Potok anonimowy łączy stdout jednego procesu ze stdin kolejnego. Jądro uruchamia oba procesy jednocześnie, używając bufora w pamięci o stałym rozmiarze (zwykle 64 KB w systemie Linux).

Najważniejszy wniosek: potok działa tak szybko, jak jego najwolniejszy etap. Jeśli producent działa szybciej, blokuje się po zapełnieniu bufora. Jeśli odbiorca działa szybciej, blokuje się przy pustym buforze. To sprzężenie zwrotne jest bezpłatnym i automatycznym mechanizmem sterowania przepływem.

Poniższy przykład zlicza unikalne adresy IP w dużym dzienniku dostępu, nigdy nie zapisując pliku tymczasowego. Każdy etap działa współbieżnie:

#!/usr/bin/env bash
# Stream a 2 GB access log — all stages run in parallel
grep '"GET' /var/log/nginx/access.log \
  | awk '{print $1}' \
  | sort \
  | uniq -c \
  | sort -rn \
  | head -20

Tworzenie potoków nazwanych za pomocą mkfifo

Potok nazwany (FIFO — First In, First Out) tworzy się za pomocą mkfifo. W systemie plików wygląda jak zwykły plik, ale zapisywane w nim dane nigdy nie są przechowywane na dysku — przepływają bezpośrednio do procesu odczytującego.

Najważniejsze zasady:

  • Zapis do FIFO blokuje się, dopóki czytelnik go nie otworzy, i odwrotnie.
  • Wpis FIFO pozostaje w systemie plików; po zakończeniu pracy należy usunąć go za pomocą rm.
  • Dozwolonych jest wielu zapisujących, ale kolejność między nimi nie jest gwarantowana.

Poniżej producent kompresuje dane do FIFO, podczas gdy odbiorca jednocześnie przesyła je do S3 — plik tymczasowy nie jest potrzebny.

#!/usr/bin/env bash
mkfifo /tmp/stream_pipe

# Producer: compress in background
gzip -c /var/log/syslog > /tmp/stream_pipe &

# Consumer: read from FIFO (runs in foreground)
wc -l < /tmp/stream_pipe

wait
rm /tmp/stream_pipe

Podstawianie procesów: traktowanie polecenia jak pliku

Podstawianie procesów korzysta ze składni <(command) lub >(command). Bash tworzy w tle FIFO (lub deskryptor pliku /dev/fd/N) i przekazuje ścieżkę do zewnętrznego polecenia.

Jest to przydatne, gdy narzędzie oczekuje argumentu będącego nazwą pliku, a nie stdin. Bez podstawiania procesów potrzebny byłby plik tymczasowy; dzięki niemu dane są przesyłane bezpośrednio strumieniowo.

  • <(cmd) — zewnętrzne polecenie odczytuje dane wyjściowe cmd.
  • >(cmd) — zewnętrzne polecenie zapisuje dane na wejściu cmd.
#!/usr/bin/env bash
# diff two sorted streams without creating temp files
diff <(sort /etc/passwd) <(sort /etc/group)

# Compare live command output against a baseline
diff <(ls /usr/bin | sort) <(cat ~/bin_baseline.txt | sort)

Tee: rozdzielanie strumienia między wielu odbiorców

tee odczytuje stdin i zapisuje dane zarówno na stdout, jak i w jednym lub wielu plikach. W połączeniu z podstawianiem procesów można rozdzielić jeden strumień na wiele potoków przetwarzania działających jednocześnie — bez zapisywania danych na dysku.

Ten wzorzec jest przydatny na przykład wtedy, gdy chcą Państwo jednocześnie rejestrować surowe dane i je przetwarzać.

#!/usr/bin/env bash
# Generate 100000 random numbers, then simultaneously:
#  1. compute the sum
#  2. find the maximum
#  3. count lines (saved to a variable)
seq 1 100000 \
  | tee >(awk '{s+=$1} END{print "Sum:", s}') \
        >(awk 'BEGIN{m=0} $1>m{m=$1} END{print "Max:", m}') \
  | wc -l | xargs echo "Count:"

Wzorzec fan-out: jeden producent, wielu odbiorców

Gdy jedno źródło danych musi zasilać wielu niezależnych odbiorców, należy połączyć tee z wieloma podstawieniami procesów >(). Każdy odbiorca otrzymuje pełny strumień i działa współbieżnie.

Pozwala to uniknąć wielokrotnego odczytywania pliku źródłowego. W przypadku pliku o rozmiarze 10 GB różnica jest ogromna — wykonywany jest jeden odczyt z dysku zamiast N odczytów.

#!/usr/bin/env bash
# Read a large CSV once; simultaneously:
#  - count rows
#  - extract column 2 to a file
#  - pass column 3 to a stats script
cat large_data.csv \
  | tee \
      >(wc -l > /tmp/row_count.txt) \
      >(cut -d',' -f2 > /tmp/col2.txt) \
      >(cut -d',' -f3 | awk '{sum+=$1} END{print sum}' > /tmp/col3_sum.txt) \
  > /dev/null

echo "Rows:" $(cat /tmp/row_count.txt)
echo "Col3 sum:" $(cat /tmp/col3_sum.txt)

Wzorzec fan-in: wielu producentów, jeden odbiorca

Odwrotnością wzorca fan-out jest fan-in: wiele niezależnych źródeł przesyła dane strumieniowo do jednego odbiorcy. Potoki nazwane znacznie ułatwiają realizację tego rozwiązania.

Częste zastosowania to scalanie strumieni dzienników z kilku serwerów w czasie rzeczywistym lub agregowanie częściowych wyników z równoległych procesów roboczych.

Należy pamiętać, że przy wielu zapisujących odbiorca widzi przeplatane dane — jest to odpowiednie w przypadku danych zorientowanych na wiersze, w których każdy wiersz jest niezależny, ale jeśli kolejność ma znaczenie, trzeba obsłużyć ją samodzielnie.

#!/usr/bin/env bash
mkfifo /tmp/fanin_pipe

# Three producers write concurrently into the same FIFO
for host in web1 web2 web3; do
  ssh "$host" 'tail -n 500 /var/log/app.log' > /tmp/fanin_pipe &
done

# Single consumer reads all merged output
grep 'ERROR' /tmp/fanin_pipe | sort | uniq -c | sort -rn

wait
rm /tmp/fanin_pipe

Używanie mkfifo do równoległej kompresji

Jednym z najbardziej praktycznych zastosowań FIFO jest kompresja równoległa. Narzędzia takie jak pigz (równoległy gzip) czy pbzip2 odczytują strumień; surowe dane można przesyłać bezpośrednio, bez tworzenia nieskompresowanego pliku pośredniego.

Poniższy wzorzec archiwizuje katalog, kompresuje go z użyciem wszystkich rdzeni procesora i przesyła wynik strumieniowo do zdalnego hosta — wszystko odbywa się jednocześnie:

#!/usr/bin/env bash
# Tar + parallel compress + stream to remote — no temp files
# Requires: pigz (parallel gzip)
tar cf - /data/large_dir \
  | pigz -p 4 \
  | ssh backup-host 'cat > /backups/large_dir.tar.gz'

# Verify the remote file exists
ssh backup-host 'ls -lh /backups/large_dir.tar.gz'

Sterowanie rozmiarem bufora i blokowaniem

Potoki mają bufor jądra (zwykle 64 KB). Gdy bufor jest pełny, zapisujący blokuje się; gdy jest pusty, blokuje się czytelnik. Zwykle jest to pożądane zachowanie, ale w niektórych sytuacjach blokowanie prowadzi do zakleszczenia.

Ryzyko zakleszczenia: jeśli proces A zapisuje do FIFO1 i odczytuje z FIFO2, a proces B zapisuje do FIFO2 i odczytuje z FIFO1, oba procesy mogą się zablokować, czekając, aż drugi najpierw odczyta dane.

Rozwiązania:

  • Umieścić co najmniej jedną stronę w tle (&), aby nie blokowała powłoki.
  • Użyć mbuffer lub pv, aby dodać większy bufor w pamięci między etapami.
  • Użyć pv -q -B 128m, aby wstawić bufor o rozmiarze 128 MB i wygładzić skoki przepustowości.
#!/usr/bin/env bash
# pv adds a 64 MB buffer and shows throughput
# Useful when producer and consumer have bursty speeds
dd if=/dev/urandom bs=1M count=200 \
  | pv -B 64m \
  | gzip \
  | wc -c

Przykład praktyczny: agregator dzienników działający w czasie rzeczywistym

Oto kompletny, realistyczny wzorzec: śledzenie wielu plików dzienników, scalanie strumieni przez potok nazwany, filtrowanie błędów i zapisywanie podsumowania na żywo — wszystko bez plików pośrednich i przy równoległym działaniu wszystkich etapów.

Takiego rodzaju potok można uruchomić jako działający w tle skrypt monitorujący na serwerze produkcyjnym.

#!/usr/bin/env bash
FIFO=/tmp/log_aggregator
mkfifo "$FIFO"

cleanup() { rm -f "$FIFO"; }
trap cleanup EXIT INT TERM

# Fan-in: tail multiple logs into the FIFO
tail -F /var/log/syslog /var/log/auth.log > "$FIFO" &
TAIL_PID=$!

# Consumer: filter and timestamp errors in real time
grep --line-buffered -i 'error\|fail\|crit' "$FIFO" \
  | while IFS= read -r line; do
      printf '[%s] %s\n' "$(date '+%H:%M:%S')" "$line"
    done

kill "$TAIL_PID" 2>/dev/null

Testowanie przepustowości potoków i plików tymczasowych

Rzeczywistą różnicę w przepustowości między podejściem strumieniowym a podejściem z plikami tymczasowymi można zmierzyć za pomocą time. Podejście potokowe wygrywa w przypadku dużych zbiorów danych, ponieważ:

  • Etapy działają współbieżnie — operacje procesora i wejścia-wyjścia nakładają się.
  • Brak operacji wejścia-wyjścia na dysku dla danych pośrednich — na dysk trafia tylko końcowy wynik.
  • Zużycie pamięci pozostaje stałe niezależnie od rozmiaru danych wejściowych (dane są przesyłane strumieniowo, a nie buforowane).

Prosty test porównujący oba podejścia:

#!/usr/bin/env bash
# Approach 1: Temp file (sequential)
time bash -c '
  seq 1 5000000 > /tmp/nums.txt
  sort -n /tmp/nums.txt > /tmp/sorted.txt
  uniq /tmp/sorted.txt | wc -l
  rm /tmp/nums.txt /tmp/sorted.txt
'

echo '---'

# Approach 2: Streaming pipeline (concurrent)
time bash -c 'seq 1 5000000 | sort -n | uniq | wc -l'

Sprawdzenie wiedzy: blokowanie potoku nazwanego

Proszę rozważyć następujący skrypt:

mkfifo /tmp/mypipe
echo 'hello' > /tmp/mypipe
echo 'done'

Co się stanie po uruchomieniu tego skryptu bez procesów działających w tle i bez czytelników?

Podsumowanie: potoki strumieniowe i nazwane potoki

W tej lekcji poznali Państwo sposoby wydajnego przesyłania danych między procesami bez używania plików pośrednich:

  • Potoki anonimowe (|) łączą sąsiadujące polecenia i uruchamiają wszystkie etapy równolegle, automatycznie stosując mechanizm back-pressure.
  • Potoki nazwane (mkfifo) tworzą wpis FIFO w systemie plików, który umożliwia wzajemne przesyłanie strumieni przez niezależne lub działające w tle procesy — zapis jest blokowany do momentu pojawienia się odbiorcy.
  • Podstawianie procesów (<(cmd), >(cmd)) pozwala poleceniom oczekującym nazw plików w sposób przezroczysty odbierać lub generować strumienie.
  • tee + >() rozdziela jeden strumień na wielu równolegle działających odbiorców bez ponownego odczytywania źródła.
  • Fan-in scala wielu producentów w jednego odbiorcę za pośrednictwem współdzielonego FIFO.
  • Back-pressure i blokowanie są funkcjami, a nie błędami — należy jednak zawsze uruchomić w tle co najmniej jedną stronę pary FIFO, aby uniknąć zakleszczenia.
  • Proszę używać pv lub mbuffer, aby dodawać większe bufory i monitorować przepustowość, gdy poszczególne etapy działają skokowo.

Techniki te stanowią podstawę wysokowydajnego przetwarzania danych w Bashu: umożliwiają przetwarzanie danych w skali GB przy stałym zużyciu pamięci oraz maksymalnym równoległym wykorzystaniu CPU i operacji wejścia-wyjścia.

Często zadawane pytania

Czy lekcja „Potoki strumieniowe i nazwane potoki zapewniające przepustowość” jest bezpłatna?

Tak — pełny tekst „Potoki strumieniowe i nazwane potoki zapewniające przepustowość” 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 Linux Command Line & Bash Scripting Mastery, przejdź na CoddyKit PRO. Kurs Linux Command Line & Bash Scripting Mastery zawiera 4 lekcji w sumie.

Co nauczysz się w „Potoki strumieniowe i nazwane potoki zapewniające przepustowość”?

Używaj FIFO i podstawiania procesów do przesyłania danych między etapami bez plików pośrednich. Ćwiczysz Linux Command Line & Bash Scripting Mastery 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ąć Linux Command Line & Bash Scripting Mastery?

Nie wymagamy żadnego doświadczenia. Linux Command Line & Bash Scripting Mastery 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 4 z 4.

Ile czasu zajmuje lekcja „Potoki strumieniowe i nazwane potoki zapewniające przepustowość”?

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 Linux Command Line & Bash Scripting Mastery?

Tak. Każda lekcja Linux Command Line & Bash Scripting Mastery 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 Linux Command Line & Bash Scripting Mastery