Идея распараллелить цикл родилась у меня в тот момент, когда я скормил моему предшествующему скрипту пару сотен маршрутизаторов. Время выполнения было оооочень большим, около часа с небольшим. Мне показалось это не правильным и я задумался как ускорить процесс.
В этой статье я не буду изменять мой предыдущий скрипт, а лишь покажу как можно распараллелить цикл в 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 минут. Согласитесь, это очень хороший показатель.
На этом у меня все. Если будут вопросы не стесняйтесь задавать, буду стараться отвечать по мере моих возможностей.
Спасибо, это то, что я искал полгода 🙂
Нужно было распараллелить конвертацию 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 — число процессов, на которые хватает мощности
(ls *.JPG) – я бы так не делал, это значит что надо находиться в этом каталоге. чисто мой совет:
grep /каталог/ -name *JPG