Параллельный запуск команд linux

Многозадачность в shell-скриптах

Иногда, при написании скрипта на shell хочется выполнять какие-то действия в несколько потоков. Подходящими ситуациями могут быть, например, сжатие большохо количества больших файлов на многопроцессорном хосте и передача файлов по широкому каналу, на котором ограничена скорость индивидуального соединения.

Все примеры написаны на bash, но (с минимальными изменениями) будут работать в ksh. В csh тоже есть средстава управления фоновыми процессами, поэтому подобных подход тоже может быть использован.

JOB CONTROL

Так называется секция в man bash где описаны подробности, на случай если вы любите читать man. Мы используем следующие простые возможности:

command & — запускает команду в фоне
jobs — печатает список фоновых команд

Простой пример, не выполняющий никаких полезных действий. Из файла test.txt читаются числа, и параллельно запускается 3 процесса, которые спят соответствующее количество секунд. Каждые три секунды проверяется число запущенных процессов, и если их меньше трех, запускается новый. Запуск фонового процесса вынесен в отдельную функцию mytask, но можно запускть его непосредственно в цикле.

#!/bin/bash NJOBS=3 ; export NJOBS function mytask () < echo sleeping for $1 sleep $1 >for i in $( cat test.txt ) do while [ $(jobs | wc -l ) -ge $NJOBS ] do sleep 3 done echo executing task for $i mytask $i & done echo waiting for $( jobs | wc -l ) jobs to complete wait

Обратите внимание на wait после цикла, команда ждет завершения исполняющихся в фоне процессов. Без нее скрипт будет завершен сразу после завершения цикла и все фоновые процессы будут прерваны. Возможно именно этот wait упоминается в известном меме «oh, wait. ».

Завершение фоновых процессов

Если прервать скрипт по Ctrl-C, он будет убит со всеми фоновыми процессами, т.к. все процессы работающие в терминале получают сигналы от клавиатуры (например, SIGINT). Если же скрипт убить из другого терминала командой kill, то фоновые процессы останутся работать до завершения и об этом нужно помнить.

Читайте также:  Acer one 10 s1002 linux

user@somehost ~/tmp2 $ ps -ef | grep -E «test|sleep»
user 1363 775 0 12:31 pts/5 00:00:00 ./test.sh
user 1368 1363 0 12:31 pts/5 00:00:00 ./test.sh
user 1370 1368 0 12:31 pts/5 00:00:00 /usr/bin/coreutils —coreutils-prog-shebang=sleep /usr/bin/sleep 60
user 1373 1363 0 12:31 pts/5 00:00:00 ./test.sh
user 1375 1373 0 12:31 pts/5 00:00:00 /usr/bin/coreutils —coreutils-prog-shebang=sleep /usr/bin/sleep 50
user 1378 1363 0 12:31 pts/5 00:00:00 ./test.sh
user 1382 1378 0 12:31 pts/5 00:00:00 /usr/bin/coreutils —coreutils-prog-shebang=sleep /usr/bin/sleep 30
user 1387 1363 0 12:31 pts/5 00:00:00 /usr/bin/coreutils —coreutils-prog-shebang=sleep /usr/bin/sleep 3
user 1389 556 0 12:31 pts/2 00:00:00 grep —colour=auto -E test|sleep
user@somehost ~/tmp2 $ kill 1363
user@somehost ~/tmp2 $ ps -ef | grep -E «test|sleep»
user 1368 1 0 12:31 pts/5 00:00:00 ./test.sh
user 1370 1368 0 12:31 pts/5 00:00:00 /usr/bin/coreutils —coreutils-prog-shebang=sleep /usr/bin/sleep 60
user 1373 1 0 12:31 pts/5 00:00:00 ./test.sh
user 1375 1373 0 12:31 pts/5 00:00:00 /usr/bin/coreutils —coreutils-prog-shebang=sleep /usr/bin/sleep 50
user 1378 1 0 12:31 pts/5 00:00:00 ./test.sh
user 1382 1378 0 12:31 pts/5 00:00:00 /usr/bin/coreutils —coreutils-prog-shebang=sleep /usr/bin/sleep 30
user 1399 556 0 12:32 pts/2 00:00:00 grep —colour=auto -E test|sleep

Эту ситуацию можно обработать, перехватывая нужные сигналы, для чего в начале скрипта добавим обработчик:

function pids_recursive() < cpids=`pgrep -P $1|xargs` echo $cpids for cpid in $cpids; do pids_recursive $cpid done >function kill_me () < kill -9 $( pids_recursive $$ | xargs ) exit 1 >#не обязательно #trap 'echo trap SIGINT; kill_me ' SIGINT trap 'echo trap SIGTERM; kill_me' SIGTERM 

kill -L выводит список существующих сигналов, при необходимости можно добавить обработчики для нужных.

Источник

MNorin.com

Блог про Linux, Bash и другие информационные технологии

Параллельное выполнение в bash

Параллельное выполнение в bash

В большинстве командных оболочек команды выполняются по умолчанию последовательно. И это, в принципе, нормально. Потому что человек с системой взаимодействует последовательно, обычно нет необходимости несколько команд выполнять параллельно. Bash в этом смысле тоже не исключение. Но при автоматизации возможность параллельного выполнения может быть полезной. Давайте посмотрим, как организовать параллельное выполнение в bash.

Читайте также:  Сменить владельца папки astra linux

Использование фонового режима

Для организации параллельной работы нескольких программ часто используется запуск в фоновом режиме при помощи знака амперсанда — &. Например:

Команда будет работать в фоне, при этом из текущей оболочки можно выполнять команды. Таким образом уже можно распараллелить какие-то действия. Можно запустить сразу несколько команд таким образом и дождаться, пока они все отработают. Для ожидания запущенных дочерних процессов используется команда wait. Эта команда без параметров ожидает окончания работы всех дочерних процессов, соответственно, для ожидания окончания 5 процессов понадобится выполнить команду всего 1 раз. В принципе, это легко реализуется через цикл. Например, так:

for i in do # запуск одного фонового процесса sleep 10 && echo $i & done # ожидание окончания работы wait echo Finished

И результат работы этого скрипта:

$ ./wait5.sh 1 5 4 2 3 Finished

Как видите, с виду одинаковые команды завершились не в том порядке, в котором мы их запустили. Давайте посмотрим теперь общее время выполнения скрипта.

$ time ./wait5.sh 4 5 2 3 1 Finished real 0m10.029s user 0m0.000s sys 0m0.008s

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

Использование пайпа

При использовании пайпов, которые перенаправляют вывод одной программы на вход другой, процессы выполняются параллельно. Отсюда вытекает еще один способ параллельного выполнения нескольких команд — перенаправить между ними какие-то данные с помощью пайпа. Например:

program1 | program2 | program3

Тут важно помнить вот что: если вам не нужно передавать реально какие-то данные между программами, то надо предварительно убедиться, что входные данные эти программы получат в виде опций командной строки и не будут использовать те данные, которые были переданы им с помощью пайпа. Хотя здесь, в принципе, возможен такой вариант:

command1 --option > dev/null | command2 param1 param2 > /dev/null | command3

Этот вариант до использования в скриптах, естественно, нужно обязательно проверить в ручном режиме и посмотреть в страницах руководств используемых программ (если там есть такая информация), что имеет больший приоритет — опции командной строки или стандартный поток ввода, потому что некоторые программы могут игнорировать командную строку, если данные передаются через стандартный поток ввода.

Читайте также:  Где лежат репозитории linux

Параллельное выполнение и ограничение количества фоновых задач в скрипте

Давайте рассмотрим такую практическую задачу — запустить 100 процессов параллельно, но так, чтобы работало одновременно не более 10 процессов. В общем, достаточно простая задача. Предположим, что все процессы работают произвольное количество времени. Пусть запуск одной задачи будет выглядеть как запуск команды sleep со случайным параметром от 0 до 29. Тогда скрипт будет выглядеть следующим образом:

#!/bin/bash RANDOM=10 JOBS_COUNTER=0 MAX_CHILDREN=10 MY_PID=$$ for i in do echo Cycle counter: $i JOBS_COUNTER=$((`ps ax -Ao ppid | grep $MY_PID | wc -l`)) while [ $JOBS_COUNTER -ge $MAX_CHILDREN ] do JOBS_COUNTER=$((`ps ax -Ao ppid | grep $MY_PID | wc -l`)) echo Jobs counter: $JOBS_COUNTER sleep 1 done sleep $(($RANDOM % 30)) & done echo Finishing children . # wait for children here while [ $JOBS_COUNTER -gt 1 ] do JOBS_COUNTER=$((`ps ax -Ao ppid | grep $MY_PID | wc -l`)) echo Jobs counter: $JOBS_COUNTER sleep 1 done echo Done

Смысл этого скрипта в целом такой: ограничиваем максимально число дочерних фоновых процессов так, чтобы их одновременно было не более 10. Как только один процесс заканчивает свою работу, запускаем следующий. И так далее, пока не выполним 100 фоновых задач. Для порядка отслеживаем в скрипте окончание работы дочерних процессов после запуска последних, только потом заканчиваем работу самого скрипта.

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

Похожие посты:

Источник

Оцените статью
Adblock
detector