Параллелизм с xargs -P и фоновыми заданиями
Запускайте независимые задачи одновременно, используя параллельный режим xargs и управляемые пулы фоновых заданий
«Параллелизм с xargs -P и фоновыми заданиями» — бесплатный урок Linux Command Line & Bash Scripting Mastery на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Linux Command Line & Bash Scripting Mastery, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Linux Command Line & Bash Scripting Mastery содержит 4 уроков всего.
Почему последовательное выполнение медленное
Когда Вы запускаете команды одну за другой в скрипте оболочки, вычислительные ядра CPU простаивают. Например, при изменении размера 500 изображений каждый вызов convert использует одно ядро, пока остальные семь простаивают.
Параллельное выполнение решает эту проблему, отправляя несколько задач одновременно. В Bash есть два основных инструмента, которые упрощают эту работу:
- xargs -P — распределяет список входных данных между N параллельными рабочими процессами
- Фоновые задания (&) + wait — позволяет вручную создавать процессы и собирать их результаты
В этом уроке рассматриваются оба подхода, чтобы Вы могли выбрать подходящий инструмент для каждой ситуации.
Повторение основ xargs
Прежде чем добавлять параллельное выполнение, вспомним, как работает xargs. Он считывает элементы из стандартного ввода и передаёт их команде в качестве аргументов.
Флаг -I {} позволяет разместить входной элемент в любом месте строки команды, а не только в конце.
В приведённом ниже примере каждый файл .txt преобразуется в верхний регистр с помощью tr. Каждый файл обрабатывается по очереди — это последовательный базовый вариант.
#!/usr/bin/env bash
# Create sample files
mkdir -p /tmp/xargs_demo
for i in 1 2 3; do
echo "hello world $i" > /tmp/xargs_demo/file$i.txt
done
# Process files one at a time (sequential)
find /tmp/xargs_demo -name '*.txt' | xargs -I {} sh -c 'tr a-z A-Z < "$1"' _ {}
# Cleanup
rm -rf /tmp/xargs_demoЗнакомство с xargs -P
Добавьте к xargs флаг -P N, чтобы запускать до N процессов параллельно. xargs автоматически управляет пулом рабочих процессов: когда один слот освобождается, следующий элемент запускается немедленно.
-P 0— создаёт столько процессов, сколько имеется входных данных (используйте осторожно для больших списков)-P 4— поддерживает работу не более 4 рабочих процессов одновременно-n 1— передаёт ровно один входной элемент каждому запущенному процессу (часто используемый дополнительный флаг)
Сочетание -n 1 -P 4 — самый распространённый шаблон: по одному элементу на рабочий процесс, всего четыре рабочих процесса одновременно.
#!/usr/bin/env bash
# Simulate 8 tasks, each taking ~1 second
# Sequential would take ~8s; parallel with -P 4 takes ~2s
process_item() {
local item="$1"
sleep 1
echo "Done: $item"
}
export -f process_item
time printf '%s\n' task{1..8} | xargs -n 1 -P 4 bash -c 'process_item "$@"' _Параллельная обработка файлов
Практический пример: одновременное сжатие множества файлов журналов. Без -P каждый вызов gzip блокирует запуск следующего. С -P 8 одновременно выполняется до восьми операций сжатия, что позволяет задействовать все ядра CPU.
Обратите внимание: -n 1 гарантирует, что каждый параллельный рабочий процесс получает ровно одно имя файла. Это особенно важно, если имена файлов могут содержать пробелы (для безопасности используйте вместе с -d '\n' или -print0 / -0).
#!/usr/bin/env bash
# Create dummy log files
mkdir -p /tmp/logs_demo
for i in $(seq 1 12); do
dd if=/dev/urandom bs=1K count=64 2>/dev/null > /tmp/logs_demo/app_$i.log
done
echo "Files before: $(ls /tmp/logs_demo | wc -l)"
# Compress all .log files in parallel (up to 8 workers)
find /tmp/logs_demo -name '*.log' -print0 \
| xargs -0 -n 1 -P 8 gzip --fast
echo "Files after : $(ls /tmp/logs_demo | wc -l)"
rm -rf /tmp/logs_demoКак выбрать значение -P
Слишком низкое значение -P приводит к простоям ядер, а слишком высокое — к чрезмерной конкуренции за ресурсы. Хорошая отправная точка — количество логических ядер CPU:
- Задачи, ограниченные CPU (сжатие, кодирование):
-P $(nproc) - Задачи, ограниченные вводом-выводом (сетевые вызовы, чтение с диска):
-P $(($(nproc) * 4))или выше, поскольку рабочие процессы большую часть времени ожидают - Задачи с ограничением по памяти: вычислите
available_RAM / task_RAM_usageи ограничьте значение этим результатом
nproc возвращает количество доступных вычислительных единиц, поэтому служит переносимой заменой жёстко заданным числам.
#!/usr/bin/env bash
CORES=$(nproc)
IO_WORKERS=$(( CORES * 4 ))
echo "CPU cores : $CORES"
echo "CPU-bound -P: $CORES"
echo "I/O-bound -P: $IO_WORKERS"
# Example: parallel curl downloads (I/O-bound)
# printf '%s\n' url1 url2 ... | xargs -n 1 -P "$IO_WORKERS" curl -sSOФоновые задания с &
Иногда Вам требуется больше контроля, чем предоставляет xargs: отдельная обработка ошибок для каждого задания, динамические списки или сложные формы аргументов. Используйте встроенный в оболочку оператор фонового выполнения &, чтобы вручную создавать задания.
Добавление & к любой команде немедленно возвращает управление скрипту. Дочерний процесс выполняется в фоне, пока родительский продолжает работу. В конце вызовите wait, чтобы дождаться завершения всех дочерних процессов.
#!/usr/bin/env bash
process() {
local id="$1"
sleep $(( RANDOM % 3 + 1 ))
echo "Job $id finished at $(date +%T)"
}
echo "Launching 5 background jobs..."
for id in $(seq 1 5); do
process "$id" &
done
wait # Block until every background job completes
echo "All jobs done."Ограничение параллельности с помощью пула заданий
Одновременный запуск всех заданий с помощью & может исчерпать память, если список велик. Пул заданий поддерживает выполнение не более N заданий одновременно:
- После запуска каждого задания проверяйте количество активных фоновых заданий с помощью
jobs -r | wc -l - Если количество достигает ограничения, вызывайте
wait -n(Bash 4.3+), чтобы дождаться завершения любого одного задания перед запуском следующего
Этот шаблон имитирует внутреннюю работу xargs -P, но предоставляет полную гибкость при написании скрипта для каждого задания.
#!/usr/bin/env bash
MAX_JOBS=3
process() {
local id="$1"
sleep $(( RANDOM % 3 + 1 ))
echo "Task $id done"
}
for id in $(seq 1 10); do
# Throttle: wait for a slot if pool is full
while (( $(jobs -r | wc -l) >= MAX_JOBS )); do
wait -n 2>/dev/null || true
done
process "$id" &
done
wait
echo "All 10 tasks complete."Получение кодов завершения параллельных заданий
Важная особенность фоновых заданий: если дочерний процесс завершается с ошибкой, родительский скрипт автоматически об этом не узнаёт. Вы должны сохранить PID каждого дочернего процесса и проверить его статус завершения с помощью wait <pid>.
В приведённом ниже шаблоне каждый PID сохраняется в массиве, после чего массив перебирается с вызовом wait "$pid", который возвращает код завершения именно этого дочернего процесса.
#!/usr/bin/env bash
set -euo pipefail
task() {
local id="$1"
sleep 1
if (( id == 3 )); then
echo "Task $id: FAILED" >&2
return 1
fi
echo "Task $id: ok"
}
pids=()
for id in $(seq 1 5); do
task "$id" &
pids+=("$!")
done
failed=0
for pid in "${pids[@]}"; do
if ! wait "$pid"; then
echo "PID $pid exited with error" >&2
(( failed++ ))
fi
done
(( failed == 0 )) && echo "All OK" || { echo "$failed job(s) failed"; exit 1; }Параллельная загрузка с помощью xargs -P
Сетевой ввод-вывод — классический пример, где полезна высокая степень параллельности: каждый рабочий процесс большую часть времени ожидает получения байтов. В приведённом ниже примере несколько URL загружаются одновременно, и каждый сохраняется в файл с уникальным именем.
Используемые основные флаги:
-P 8— восемь одновременно выполняющихся процессов curl-n 1— один URL на каждый вызов curl--create-dirs -o— curl сохраняет результат в производное имя файла
#!/usr/bin/env bash
# Download several small public files in parallel
URLs=(
"https://httpbin.org/bytes/1024"
"https://httpbin.org/bytes/2048"
"https://httpbin.org/bytes/512"
"https://httpbin.org/bytes/4096"
)
mkdir -p /tmp/parallel_dl
printf '%s\n' "${URLs[@]}" | xargs -n 1 -P 4 bash -c '
url="$1"
out="/tmp/parallel_dl/$(echo "$url" | md5sum | cut -c1-8).bin"
curl -sSf "$url" -o "$out" && echo "Saved $out"
' _
ls -lh /tmp/parallel_dl/
rm -rf /tmp/parallel_dlОбъединение find, xargs -P и функций оболочки
Чтобы использовать многострочную функцию оболочки с xargs, необходимо экспортировать её с помощью export -f function_name, а затем вызвать через bash -c 'function_name "$@"' _ внутри xargs.
Этот шаблон открывает доступ ко всем возможностям написания скриптов внутри каждого параллельного рабочего процесса: ведение журнала, обработка ошибок и условная логика — отдельно для каждого элемента.
#!/usr/bin/env bash
mkdir -p /tmp/proc_demo
for i in $(seq 1 8); do echo "data $i" > /tmp/proc_demo/item_$i.txt; done
process_file() {
local f="$1"
local base
base=$(basename "$f" .txt)
# Simulate work: count words and append a timestamp
local wc
wc=$(wc -w < "$f")
echo "[$base] words=$wc processed=$(date +%T)" >> "/tmp/proc_demo/${base}.result"
}
export -f process_file
find /tmp/proc_demo -name '*.txt' -print0 \
| xargs -0 -n 1 -P "$(nproc)" bash -c 'process_file "$@"' _
grep '' /tmp/proc_demo/*.result
rm -rf /tmp/proc_demoИзмерение ускорения с помощью time
Всегда проводите измерения, прежде чем заявлять об улучшении. Оберните параллельную команду в time и сравните результат с последовательным базовым вариантом. Фактическое ускорение зависит от следующих факторов:
- Независимость задач — задачи не должны совместно изменять состояние без блокировок
- Накладные расходы — стоимость запуска процесса (примерно 5–20 мс на каждый) важна для маленьких задач
- Конкуренция за ресурсы — ввод-вывод диска может достичь предела ещё до загрузки CPU
Ниже показан простой шаблон измерения: сначала выполните команду последовательно, затем параллельно и сравните реальное время real.
#!/usr/bin/env bash
work() { sleep 0.2; } # simulate a 200ms task
export -f work
ITEMS=$(seq 1 16)
echo "=== Sequential ==="
time printf '%s\n' $ITEMS | xargs -n 1 bash -c 'work' _
echo
echo "=== Parallel ($(nproc) workers) ==="
time printf '%s\n' $ITEMS | xargs -n 1 -P "$(nproc)" bash -c 'work' _Проверка знаний: поведение xargs -P
Проверьте, насколько хорошо Вы понимаете параллельное выполнение с помощью xargs -P.
Итоги урока
Теперь Вам известны два надёжных способа параллельного выполнения в Bash:
- xargs -n 1 -P N — самый простой подход; xargs автоматически управляет пулом рабочих процессов. Лучше всего подходит, когда входные данные представляют собой обычный список, а каждому элементу соответствует одна команда.
- Фоновые задания (&) + wait — полный контроль над скриптом; необходим, когда нужны PID отдельных заданий, динамический ввод или детальная обработка кодов завершения. Используйте
wait -nсо счётчиком, чтобы ограничивать параллельность.
Основные правила:
- Экспортируйте функции оболочки с помощью
export -f, прежде чем передавать их черезxargs - Используйте
-print0/-0для безопасной обработки имён файлов с пробелами - Сохраняйте PID в массиве и вызывайте
wait "$pid"отдельно для каждого элемента, чтобы обнаруживать ошибки - Проводите измерения с помощью
time: параллельность даёт выигрыш только тогда, когда накладные расходы задачи превышают стоимость запуска процесса - Для задач, ограниченных CPU, устанавливайте
-P $(nproc), а для задач, ограниченных вводом-выводом, используйте больший множитель
Часто задаваемые вопросы
Урок «Параллелизм с xargs -P и фоновыми заданиями» бесплатный?
Да — полный текст урока «Параллелизм с xargs -P и фоновыми заданиями» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Linux Command Line & Bash Scripting Mastery, подпишись на CoddyKit PRO. Курс Linux Command Line & Bash Scripting Mastery содержит 4 уроков всего.
Чему я научусь в уроке «Параллелизм с xargs -P и фоновыми заданиями»?
Запускайте независимые задачи одновременно, используя параллельный режим xargs и управляемые пулы фоновых заданий Ты практикуешь Linux Command Line & Bash Scripting Mastery с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Linux Command Line & Bash Scripting Mastery?
Предыдущий опыт не требуется. Linux Command Line & Bash Scripting Mastery на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.
Сколько времени занимает урок «Параллелизм с xargs -P и фоновыми заданиями»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Linux Command Line & Bash Scripting Mastery?
Да. Каждый урок Linux Command Line & Bash Scripting Mastery включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Профилирование скриптов и отказ от ненужных подоболочек
- Параллелизм с xargs -P и фоновыми заданиями
- Оркестрация рабочих нагрузок с GNU parallel
- Потоковые конвейеры и именованные каналы для пропускной способности