Как распараллелить цикл в bash

Идея распараллелить цикл родилась у меня в тот момент, когда я скормил моему предшествующему скрипту пару сотен маршрутизаторов. Время выполнения было оооочень большим, около часа с небольшим. Мне показалось это не правильным и я задумался как ускорить процесс.

В этой статье я не буду изменять мой предыдущий скрипт, а лишь покажу как можно распараллелить цикл в bash.

И так имеем следующий цикл:

while read line
do
program &line
done < fileinput.txt

тут все просто, цикл читает каждую линию в файле «fileinput.txt» и подставляет эту линию в опцию для команды «program». Как только команда «program» завершится то цикл подставляет следующую строчку из файл «fileinput.txt» команде «program» и так пока строчки в файле не закончатся.

Таким образом наша команда за раз выполняется однажды и скорее всего загружает всего одно ядро в нашем процессе, а остальные тупо простаивают.

Для распараллеливания и загрузки всех ядер я предлагаю внести небольшие изменения в наш цикл:

while read line
do
program &line &
[ $( jobs | wc -l ) -ge $( nproc ) ] && wait
done < fileinput.txt
wait

Давайте разберемся что я добавил

Во-первых, я добавил амперсант & в конец нашей команды с параметром. Это говорит системе не ждать окончания команды и двигаться дальше, поместив выполнение команды в фоновый режим.

В Linux все процессы в фоновом режиме помечаются особым образом и их можно просмотреть командой jobs. Вы наверно уже поняли к чему я виду? Нет? Поясняю.

Следующей строкой мы говорим командой jobs показать все процессы которые выполняются в фоне, конвейер | передает эту информацию команде wc –l которая просто складывает полученные данные и выводит их. В итоге мы получаем количество фоновых процессов.

Наверно вы заметили, что это все выполняется в квадратных скобках, а квадратные скобки в bash это ничто иное как команда test. В нашем случаи эта команда имеет параметр –ge и если вы посмотрите в man, то узнаете что это значит следующее:

ЦЕЛОЕ1 больше или равно ЦЕЛОЕ2

А команда nproc просто выводит количество ядер в системе.

Что мы получаем? Правильно, количество фоновых процессов больше или равно количеству ядер.

Затем идет два амперсанта && которые говорят команде test что ты можешь продолжить выполнение если твоя задача выполнена корректно. Таким образом пока количество фоновых процессов больше или равно количеству ядер цикл пойдет дальше и остановится на команде wait.

Команда wait бесконечна и соответственно цикл будет стоять пока один или несколько фоновых процессов не завершаться и тогда цикл запустила снова, команда тест завершится с ошибкой, а как я говорил ранее && два этих амперсанта не перейдут в выполнение команды wait и цикл продолжит свою работу.

$( nproc ) – можно заменить на любое нужное вам число например числом 100 и тогда количество запущенных процессов будет равно 100.

Для своего предыдущего скрипта я установил этот параметр в 5 и время выполнения сократилось с более часа в 15 минут. Согласитесь, это очень хороший показатель.

На этом у меня все. Если будут вопросы не стесняйтесь задавать, буду стараться отвечать по мере моих возможностей.

У этой записи 2 комментариев

  1. Спасибо, это то, что я искал полгода 🙂
    Нужно было распараллелить конвертацию jpeg файлов в avif (очень cpu-емкий процесс). Решил этой командой:
    for file in $(ls *.JPG); do magick ${file} -format “AVIF” -quality 50 ~/output/${file}.avif & [ $( jobs | wc -l ) -ge 4 ] && wait; done

    Где 4 — число процессов, на которые хватает мощности

    1. (ls *.JPG) – я бы так не делал, это значит что надо находиться в этом каталоге. чисто мой совет:
      grep /каталог/ -name *JPG

Добавить комментарий

4 + пятнадцать =