Ветвление процессов

vetvlenie processov Системные инструменты параллельного выполнения

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

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

Возможно, это проще понять на примере, чем в теории. Сценарий Python в примере 5.1 продолжает ответвлять новые дочерние процессы, пока в консоли не будет нажата клавиша q.

Пример 5.1. PP4E\System\Processes\fork1.py

“ответвляет дочерние процессы, пока не будет нажата клавиша q’”

import os

def child():

print(‘Hello from child’, os.getpid())

os._exit(0) # иначе произойдет возврат в родительский цикл

def parent():

while True:

newpid = os.fork()

if newpid == 0:

child()

else:

print(‘Hello from parent’, os.getpid(), newpid) if input() == ‘q’: break

parent()

Инструменты ветвления процессов в Python, находящиеся в модуле os, — это просто тонкие обертки вокруг стандартных средств ветвления из системной библиотеки, используемой также программами на языке C. Запуск нового параллельного процесса осуществляется вызовом функции os.fork. Поскольку эта функция создает копию вызывающей программы, она возвращает различные значения в каждой копии: ноль — в дочернем процессе и числовой идентификатор ID процесса нового потомка — в родительском процессе.

Обычно программы проверяют этот результат, чтобы приступить к выполнению каких-то операций только в дочернем процессе. В этом сценарии, например, функция child вызывается только в дочерних процессах.[XIV]

Поскольку ветвление процессов исходно является частью модели программирования в Unix, этот сценарий замечательно будет функционировать в Unix, Linux и в современных версиях Mac OS. К сожалению, этот сценарий не будет работать под управлением стандартной версии Python в Windows, потому что функция fork не стыкуется с моделью Windows. Тем не менее в Windows сценарии на языке Python всегда могут порождать потоки выполнения, а также использовать пакет multiprocessing, описываемый ниже в этой главе. Этот модуль обеспечивает альтернативный и переносимый способ запуска процессов, который позволяет отказаться от приема ветвления процессов в Windows в контекстах, согласующихся с его ограничениями (хотя и за счет необходимости выполнения некоторых низкоуровневых операций).

Однако сценарий из примера 5.1 будет работать в Windows, если использовать версию Python, распространяемую вместе с системой Cygwin (или собранную вами из исходных текстов вместе с библиотеками Cygwin). Cygwinэто бесплатная и открытая система, обеспечивающая полную Unix-подобную функциональность для Windows (описывается ниже, во врезке «Подробнее о Cygwin Python для Windows»). Используя Python для Cygwin в операционной системе Windows, можно использовать прием ветвления процессов, хотя он не полностью соответствует приему ветвления процессов в Unix. Однако, поскольку эта версия Python достаточно близка к рассматриваемым в данной книге, давайте воспользуемся ею, чтобы запустить сценарий:

[C:\\PP4E\System\Processes]$ python fork1.py

Hello from parent 7296 7920

Hello from child 7920

Hello from parent 7296 3988

Hello from child 3988

Hello from parent 7296 6796

Hello from child 6796

q

Эти сообщения представляют три ответвленных дочерних процесса — уникальные идентификаторы всех участвующих процессов получены и выведены с помощью функции os.getpid. Важно отметить, что вызов функции child в дочернем процессе явно завершает его выполнение вызовом функции os._exit. Эту функцию мы более подробно обсудим далее в этой главе, но если ее не вызвать, дочерний процесс продолжит существование после возврата из функции child (не забывайте, что это лишь копия исходного процесса). В этом случае дочерний процесс возвратится в цикл, находящийся в функции parent, и начнет плодить собственных потомков (то есть у родителя появятся внуки). Если удалить вызов выхода и перезапустить сценарий, то для его остановки может понадобиться несколько раз нажать клавишу q, поскольку несколько процессов будут выполнять функцию parent.

В примере 5.1 каждый процесс завершается вскоре после запуска, поэтому перекрытие по времени незначительно. Попробуем сделать нечто более сложное, чтобы лучше продемонстрировать параллельное выполнение нескольких ответвленных процессов. Пример 5.2 запускает 5 копий себя самого, при этом каждая копия считает до 5 с односекундной задержкой между итерациями. Функция time.sleep из стандартной библиотеки просто приостанавливает работу вызывающего процесса на указанное количество секунд (допускается указывать значение с плавающей точкой, чтобы приостановить процесс на дробную часть секунды).

Пример 5.2. PP4E\System\Processes\fork-count.py

Основы ветвления: запустить 5 копий этой программы параллельно оригиналу; каждая копия считает до 5 и выводит счетчик в тот же поток stdout — при ветвлении копируется память процесса, в том числе дескрипторы файлов; в настоящее время ветвление не действует в Windows без Cygwin: запускайте программы в Windows с помощью функции os.spawnv или пакета multiprocessing; функция spawnv примерно соответствует комбинации функций fork+exec;

import os, time

def counter(count): # вызывается в новом процессе

for i in range(count): time.sleep(1) # имитировать работу

print(‘[%s] => %s’ % (os.getpid(), i))

for i in range(5):

pid = os.fork()

if pid != 0: # в родительском процессе:

print(‘Process %d spawned’ % pid) # продолжить цикл

else:

counter(5) # в дочернем процессе

os._exit(0) # вызвать функцию и завершиться

print(‘Main process exiting.’) # родитель не должен ждать

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

[C:\\PP4E\System\Processes]$ python fork-count.py

Process 4556 spawned

Process 3724 spawned

Process 6360 spawned Process 6476 spawned Process 6684 spawned Main process exiting. [4556] => 0 [3724] => 0 [6360] => 0 [6476] => 0 [6684] => 0 [4556] => 1 [3724] => 1 [6360] => 1 [6476] => 1 [6684] => 1 [4556] => 2 [3724] => 2 [6360] => 2 [6476] => 2 [6684] => 2

…остальная часть вывода опущена…

Вывод всех этих процессов отображается на одном и том же экране, потому что все они используют стандартный поток вывода (в процессе работы периодически может появляться системное приглашение к вводу). Технически ответвленный процесс получает копию глобальной памяти оригинального процесса, в том числе дескрипторы открытых файлов. Из-за этого глобальные объекты, такие как файлы, начинают работу в дочернем процессе с одними и теми же значениями, поэтому все процессы в этом примере оказываются подключенными к одному и тому же потоку вывода. Но важно помнить, что глобальная память копируется, а не используется совместно, — если дочерний процесс изменит глобальный объект, то изменит только свою копию этого объекта. (Как мы увидим, в потоках выполнения все происходит совсем иначе. Это тема следующего раздела.)

Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, I том, 2011

Оцените статью
Секреты программирования
Добавить комментарий