В чем разница между “progA &”, “progB && progA &” и “( progB && progA ) &” в bash с точки зрения завершения дочерних процессов с помощью “pkill -P”?

Вопрос или проблема

Ограничения:

  • Я не могу хранить PID дочерних процессов никаким образом;
  • Я не могу наследовать trap;
  • Я не могу использовать программы вне coreutils/util-linux (возможно с исключением для pkill).

Постановка задачи:

У меня есть bash-скрипт, который запускает произвольное количество программ следующим образом:

progA &
mkdir -p dirB && progB --arg=dirB &
progC &
...
progF

Я хочу убедиться, что когда основной bash-скрипт завершает работу (например, из-за SIGTERM), все дочерние процессы завершаются.

Что я пробовал:

Пока что я пробовал использовать ловушки такие как trap "trap - TERM ; kill 0" EXIT INT TERM (плюс что-то вроде kill -- -$$).

Единственная ловушка, которая работает надежно, это pkill -P $$
за исключением случая, когда программа запускается так mkdir -p dirB && progB &
но когда она запускается в подконсоли как ( mkdir -p dirB && progB ) &
progB завершается надежно.

Почему это так?

Уточнение: когда я говорю ‘надежно’, я имею в виду всегда. Если я запускаю и быстро останавливаю скрипт, то progB не завершается в чем-то вроде 1/10 случаев.

Версия оболочки: GNU bash, версия 5.2.37(1)-release (x86_64-pc-linux-gnu)

Ответ или решение

Теория

В Bash символ & используется для запуска команды в фоновом режиме, а && — для условного выполнения следующей команды только в случае успешного завершения предыдущей. Скобки ( ... ) создают подшелл, позволяя группировать команды для выполнения в отдельной среде. Когда дело доходит до управления процессами и их завершения в Unix-подобных системах, важном инструментом является команда pkill, особенно с опцией -P, позволяющей завершать процессы по идентификатору родительского процесса (PID) (в данном случае — PID основного скрипта).

Пример

Команда progA &

Выполнение progA & запускает progA в фоновом режиме как отдельный процесс, дочерний к текущему shell-скрипту. Родительским процессом для progA является сам скрипт. Таким образом, при использовании pkill -P $$ этот процесс будет правильно завершен, поскольку его родитель — основной скрипт.

Команда progB && progA &

Когда выполняется progB && progA &, progB запускается в основном shell, и только если он успешно завершится, будет запущен progA в фоновом режиме. В этой конструкции родительский процесс для progA также является основным скриптом, но поскольку progA запущен после успешного выполнения progB, существует небольшая вероятность, что progA не успевает стать дочерним процессом основного скрипта из-за временных задержек. Это может объяснить, почему progA иногда не завершается при быстром завершении основного скрипта.

Команда ( progB && progA ) &

Конструкция (progB && progA) & создаёт подшелл для выполнения progB и progA. Подшелл сам по себе становится независимым фоновым процессом. В этом случае подшелл является дочерним процессом основного скрипта, в то время как progB и progA становятся дочерними процессами подшелла. При использовании pkill -P $$, все дочерние процессы основного скрипта, включая подшелл, будут завершены. Поскольку progA и progB выполняются в контексте подшелла, они также будут завершены, так как их родительский процесс (подшелл) завершен.

Применение

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

(progB && progA) &

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

Выводы

Таким образом, ключевое различие между этими командами — это структура родительских и дочерних процессов, создаваемых ими. Основная цель должна заключаться в том, чтобы каждая группа процессов имела общего родителя, которого легко контролировать и завершать. Использование подшеллов предоставляет более надёжный механизм создания и завершения процессов в Unix-подобных системах, таких как Linux, что особенно важно в сценариях, требующих высокой стабильности и предсказуемости в управлении жизненным циклом процессов.

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

Капча загружается...