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

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

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

JOB CONTROL


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

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

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

test.sh
#!/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


Входные данные:

test.txt
60
50
30
21
12
13

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

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


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

Заголовок спойлера
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

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

trap
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 выводит список существующих сигналов, при необходимости можно добавить обработчики для нужных.

Only registered users can participate in poll. Log in, please.

Была ли статья полезной

  • 65.0%Старо как shell26
  • 37.5%Узнал что-то новое15

Similar posts

Ads
AdBlock has stolen the banner, but banners are not teeth — they will be back

More

Comments 9

    0
    ИМХО, но если необходима многозадачность, то лучше взять нормальный язык программирования и написать логику на нем. В баше это реализовано ужасно и любой скрипт на 200+ строк и многопоточностью будет нечитаемой кучей кода. (Это не относится к xargs -P или parallel). Контроль кодов выхода, синхронизация потоков, доступ к общим данным. Конечно все это можно заколхозить на баше, но как сисадмин, я надеюсь, что мне не достанется такой подарок на поддержку.
      0
      Соглашусь. Мелкие задачки, как правило, не требуют распараллеливания, а для сложных уже нужен более серьёзный инструмент.
      0

      Хм… Моя нужда в многозадачности на баше пока примерно исчерпывается однострочниками вида
      ( команда_1 & команда_2 & wait )


      (пинговал в параллель роутер и провайдера, чтобы вывод в одну консоль шёл и по Ctrl-C все джобы остановились. Из забавного – в сети для такой простой задачи предлагался ужОс с trap).

        0

        fping решит эту задачу без привлечения баш-фу.

          0

          Спасибо, не знал про него. brew install fping поехал :-)
          (заодно узнал про корявку в zsh: сразу после установки консольной утилиты – он не подтягивает autocomplete для неё, надо или запускать новый zsh, или звать rehash)


          Но, тем не менее, подобные однострочники могут пригодиться, и по моим меркам это ещё не bash-fu, запутаться в них сложно.

        0
        Хм, даже тема `wait’ не раскрыта, не говоря уж о настоящих развлечениях…
          0
          Что, по вашему мнению, нужно было написать про wait для раскрытия темы?
            0
            Хм, не, ну Вас вредный бесполезный не шибко полезный `jobs' жирным шрифтом и в начале, а `wait', типа, мимо проходил.

            Если в деле чистого POSIX, то хотя бы за аргументы слово замолвить, как и за код возврата, а в деле ужасных башизмов, то и за ключи полезные.
            j1&
            p1=$!
            j2&
            wait $p1
            echo Job 1 exited with status $?
            wait $!
            echo Job 2 exited with status $?
            


            За псевдопеременную `$!' вообще ни полслова, как и за вопросы синхронизации…
            https://pubs.opengroup.org/onlinepubs/9699919799/utilities/wait.html
              0
              jobs в скрипте используется для того, чтобы контролировать сколько фоновых заданий выполняется в настоящий момент, не такой уж бесполезный. Абзац про wait начинается с «обратите внимание». Но, согласен, wait стоило добавить к списку используемых команд.
              Статья предполагалась как туториал по конкретному приему. Не сомневаюсь, что вы можете лучше и интереснее, напишите.

        Only users with full accounts can post comments. Log in, please.