Как запустить заданную функцию в Bash параллельно?
Были некоторые аналогичные вопросы, но моя проблема заключается не в том, чтобы "запускать несколько программ параллельно" - это можно сделать тривиально с помощью parallel
или xargs
.
Мне нужно распараллелить функции Bash.
Представьте себе такой код:
for i in "${list[@]}"
do
for j in "${other[@]}"
do
# some processing in here - 20-30 lines of almost pure bash
done
done
Некоторая обработка требует вызовов внешних программ.
Я хотел бы запустить некоторые (4-10) задачи, каждый из которых работает для разных $i
. Общее количество элементов в $list составляет > 500.
Я знаю, что могу поместить весь цикл for j ... done
во внешний script и просто вызвать эту программу параллельно, но можно ли обойтись без разделения функций между двумя отдельными программами?
Ответы
Ответ 1
Изменить: Пожалуйста, рассмотрите Ole answer.
Вместо отдельного script вы можете поместить свой код в отдельную функцию bash. Затем вы можете экспортировать его и запустить через xargs:
#!/bin/bash
dowork() {
sleep $((RANDOM % 10 + 1))
echo "Processing i=$1, j=$2"
}
export -f dowork
for i in "${list[@]}"
do
for j in "${other[@]}"
do
printf "%s\0%s\0" "$i" "$j"
done
done | xargs -0 -n 2 -P 4 bash -c 'dowork "[email protected]"' --
Ответ 2
sem
является частью GNU Parallel и используется для такого рода ситуаций.
for i in "${list[@]}"
do
for j in "${other[@]}"
do
# some processing in here - 20-30 lines of almost pure bash
sem -j 4 dolong task
done
done
Если вам нравится функция лучше, GNU Parallel может сделать двойной цикл for за один раз:
dowork() {
echo "Starting i=$1, j=$2"
sleep 5
echo "Done i=$1, j=$2"
}
export -f dowork
parallel dowork ::: "${list[@]}" ::: "${other[@]}"
Ответ 3
Решение для параллельной работы нескольких строк:
for i in "${list[@]}"
do
for j in "${other[@]}"
do
test "$(jobs | wc -l)" -ge 8 && wait -n || true
(
your
multi-line
commands
here
) &
done
done
Если уже запущено 8 bash заданий, wait
будет ждать завершения хотя бы одного задания. Если/при меньшем количестве заданий, он запускает новые асинхронно.
Преимущества такого подхода:
- Это очень легко для многострочных команд. Все ваши переменные автоматически "захватываются" в области видимости, не нужно передавать их в качестве аргументов
- Это относительно быстро. Сравните это, например, с параллельным (я цитирую официальный
man
):
Параллельная параллель медленна при запуске - около 250 мс в первый раз и 150 мс после этого.
- Для работы требуется
bash
.
Downsides:
- Есть вероятность, что было 8 заданий, когда мы их подсчитали, но меньше, когда мы начали ждать. (Это происходит, если задание заканчивается в эти миллисекунды между двумя командами.) Это может сделать нас
wait
с меньшим количеством заданий, чем требуется. Тем не менее, он возобновится, когда по крайней мере одно задание завершится или сразу же, если есть 0 заданий (wait -n
немедленно выйдет в этом случае).
- Очень маловероятно, но если вы используете bash управление заданиями (
&
) для других целей в пределах одного цикла, все может работать не так, как ожидалось.