Потоковые конвейеры и именованные каналы для пропускной способности
Используйте FIFO и подстановку процессов, чтобы передавать данные между этапами без промежуточных файлов
«Потоковые конвейеры и именованные каналы для пропускной способности» — бесплатный урок Linux Command Line & Bash Scripting Mastery на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Linux Command Line & Bash Scripting Mastery, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Linux Command Line & Bash Scripting Mastery содержит 4 уроков всего.
Почему промежуточные файлы снижают пропускную способность
Когда Вы объединяете команды вроде sort file.txt > tmp.txt && uniq tmp.txt > result.txt, возникают скрытые затраты: запись на диск, чтение с диска и остановка конвейера до полного завершения первого этапа перед началом следующего.
Потоковые конвейеры устраняют эти затраты. Данные передаются непосредственно от источника к получателю в памяти, поэтапно и одновременно. Это основная идея каналов Unix, а именованные каналы (FIFO) развивают её ещё дальше.
- Анонимный канал (
|): соединяет две соседние команды в одной строке оболочки. - Именованный канал (FIFO): специальный файл в файловой системе, позволяющий независимым процессам передавать данные друг другу потоком.
- Подстановка процесса: позволяет команде обращаться с выводом другой команды так, будто это файл.
В этом уроке показано, как применять все три подхода, чтобы максимально повысить пропускную способность в практических сценариях Bash.
Устройство потокового конвейера
Анонимный канал соединяет стандартный вывод одного процесса со стандартным вводом следующего. Ядро одновременно поддерживает выполнение обоих процессов, используя буфер фиксированного размера в памяти (обычно 64 KB в Linux).
Главная идея: скорость конвейера определяется его самым медленным этапом. Если источник работает быстрее, он блокируется при заполнении буфера. Если получатель работает быстрее, он блокируется при пустом буфере. Это обратное давление — бесплатное автоматическое управление потоком.
Пример ниже подсчитывает уникальные IP-адреса в большом журнале доступа, ни разу не записывая временный файл. Каждый этап выполняется одновременно:
#!/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Создание именованных каналов с помощью mkfifo
Именованный канал (FIFO — First In, First Out) создаётся с помощью mkfifo. В файловой системе он выглядит как обычный файл, но записываемые в него данные не сохраняются на диске — они напрямую передаются процессу чтения.
Запомните основные свойства:
- Запись в FIFO блокируется, пока его не откроет читатель, и наоборот.
- Запись FIFO сохраняется в файловой системе; после завершения работы её необходимо удалить с помощью
rm. - Допускается несколько записывающих процессов, но порядок данных между ними не гарантируется.
Ниже источник одновременно сжимает данные в FIFO, а получатель загружает их в S3 — временный файл не нужен.
#!/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Подстановка процесса: обращение с командой как с файлом
Подстановка процесса использует синтаксис <(command) или >(command). Bash создаёт за кулисами FIFO (или дескриптор файла /dev/fd/N) и передаёт внешний команде путь к нему.
Это особенно удобно, когда инструмент ожидает аргумент имени файла, а не стандартный ввод. Без подстановки процесса потребовался бы временный файл; с ней данные передаются напрямую потоком.
<(cmd)— внешняя команда читает вывод cmd.>(cmd)— внешняя команда записывает во входной поток 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: разделение потока между несколькими получателями
tee читает стандартный ввод и записывает его одновременно в стандартный вывод и в один или несколько файлов. В сочетании с подстановкой процесса можно направить один поток в несколько конвейеров обработки одновременно — и всё это без обращения к диску.
Этот шаблон полезен, например, когда нужно одновременно записывать необработанные данные в журнал и обрабатывать их.
#!/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:"Шаблон разветвления: один источник, несколько получателей
Если один источник данных должен передавать данные нескольким независимым получателям, объедините tee с несколькими подстановками процессов >(). Каждый получатель получает полный поток и работает одновременно с остальными.
Это позволяет не читать исходный файл несколько раз. Для файла размером 10 GB разница огромна: выполняется одно чтение с диска вместо N чтений.
#!/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)Шаблон объединения: несколько источников, один получатель
Обратный вариант разветвления — объединение: несколько независимых источников передают данные одному получателю. Именованные FIFO делают это простым.
Распространённый сценарий — объединение потоков журналов с нескольких серверов в реальном времени или сбор частичных результатов параллельных рабочих процессов.
Учтите, что при наличии нескольких записывающих процессов получатель видит перемешанный вывод. Это подходит для построчных данных, где каждая строка самодостаточна, но если порядок важен, управлять им необходимо самостоятельно.
#!/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Использование mkfifo для параллельного сжатия
Одно из самых практичных применений FIFO — параллельное сжатие. Инструменты вроде pigz (параллельный gzip) или pbzip2 читают поток; необработанные данные можно передавать напрямую, не создавая несжатый файл.
Приведённый ниже шаблон архивирует каталог, сжимает его с использованием всех ядер CPU и одновременно передаёт результат на удалённый узел:
#!/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'Управление размером буфера и блокировками
Каналы используют буфер ядра (обычно 64 KB). Когда буфер заполнен, записывающий процесс блокируется; когда буфер пуст, блокируется читающий процесс. Обычно это именно то, что нужно, но в некоторых случаях блокировка приводит к взаимной блокировке.
Риск взаимной блокировки: если процесс A записывает в FIFO1 и читает из FIFO2, а процесс B записывает в FIFO2 и читает из FIFO1, оба процесса могут заблокироваться, ожидая, пока другой сначала выполнит чтение.
Решения:
- Запустите хотя бы одну сторону в фоновом режиме (
&), чтобы она не блокировала оболочку. - Используйте
mbufferилиpv, чтобы добавить между этапами буфер большего размера в памяти. - Используйте
pv -q -B 128m, чтобы вставить буфер размером 128 MB и сгладить скачки пропускной способности.
#!/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Практический пример: агрегатор журналов в реальном времени
Вот полный реалистичный шаблон: отслеживать несколько файлов журналов, объединять потоки через именованный канал, отбирать ошибки и записывать сводку в реальном времени — без промежуточных файлов и при одновременном выполнении всех этапов.
Такой конвейер можно запустить как фоновый сценарий мониторинга на производственном сервере.
#!/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Сравнительное измерение производительности конвейеров и временных файлов
Измерить реальную разницу в пропускной способности потокового подхода и подхода с временными файлами можно с помощью time. На больших наборах данных конвейер обычно выигрывает, потому что:
- Этапы выполняются одновременно — CPU и ввод-вывод перекрываются.
- Промежуточные данные не записываются на диск — на диск попадает только окончательный вывод.
- Объём используемой памяти остаётся постоянным независимо от размера входных данных (данные передаются потоком, а не буферизуются целиком).
Простое измерение для сравнения обоих подходов:
#!/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'Проверка знаний: блокировка именованного канала
Рассмотрим следующий сценарий:
mkfifo /tmp/mypipe
echo 'hello' > /tmp/mypipe
echo 'done'Что произойдёт при запуске этого сценария без фоновых процессов и читателей?
Повторение: потоковые конвейеры и именованные каналы
В этом уроке Вы изучили, как эффективно перемещать данные между процессами без промежуточных файлов:
- Анонимные каналы (
|) соединяют соседние команды и одновременно запускают все этапы с автоматическим обратным давлением. - Именованные каналы (
mkfifo) создают запись FIFO в файловой системе, позволяющую независимым или работающим в фоновом режиме процессам обмениваться потоками данных — запись блокируется, пока не появится читатель. - Подстановка процессов (
<(cmd),>(cmd)) позволяет командам, ожидающим имена файлов, прозрачно получать или создавать потоки данных. tee+>()разветвляет один поток на нескольких одновременно работающих получателей без повторного чтения источника.- Объединение потоков объединяет данные от нескольких производителей для одного получателя через общий FIFO.
- Обратное давление и блокировка — это возможности, а не ошибки, однако хотя бы одну сторону пары FIFO всегда следует запускать в фоновом режиме, чтобы избежать взаимной блокировки.
- Используйте
pvилиmbuffer, чтобы добавить буферы большего размера и отслеживать пропускную способность, когда этапы работают пакетами.
Эти методы лежат в основе высокопроизводительной обработки данных в Bash: обрабатывайте данные масштаба GB с постоянным потреблением памяти и максимальным параллелизмом CPU/IO.
Часто задаваемые вопросы
Урок «Потоковые конвейеры и именованные каналы для пропускной способности» бесплатный?
Да — полный текст урока «Потоковые конвейеры и именованные каналы для пропускной способности» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Linux Command Line & Bash Scripting Mastery, подпишись на CoddyKit PRO. Курс Linux Command Line & Bash Scripting Mastery содержит 4 уроков всего.
Чему я научусь в уроке «Потоковые конвейеры и именованные каналы для пропускной способности»?
Используйте FIFO и подстановку процессов, чтобы передавать данные между этапами без промежуточных файлов Ты практикуешь Linux Command Line & Bash Scripting Mastery с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Linux Command Line & Bash Scripting Mastery?
Предыдущий опыт не требуется. Linux Command Line & Bash Scripting Mastery на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.
Сколько времени занимает урок «Потоковые конвейеры и именованные каналы для пропускной способности»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Linux Command Line & Bash Scripting Mastery?
Да. Каждый урок Linux Command Line & Bash Scripting Mastery включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Профилирование скриптов и отказ от ненужных подоболочек
- Параллелизм с xargs -P и фоновыми заданиями
- Оркестрация рабочих нагрузок с GNU parallel
- Потоковые конвейеры и именованные каналы для пропускной способности