perlfork
СОДЕРЖАНИЕ
- ИМЯ
- СИНОПСИС
- ОПИСАНИЕ
- ОСОБЕННОСТИ И ОГРАНИЧЕНИЯ
- ОСОБЕННОСТИ ПЕРЕНОСИМОСТИ
- ОШИБКИ
- АВТОР
- СМОТРИТЕ ТАКЖЕ
ИМЯ
perlfork - эмуляция fork() в Perl
СИНОПСИС
NOTE: As of the 5.8.0 release, fork() emulation has considerably
matured. However, there are still a few known bugs and differences
from real fork() that might affect you. See the "BUGS" and
"CAVEATS AND LIMITATIONS" sections below. Perl предоставляет ключевое слово fork(), соответствующее системному вызову Unix с тем же именем. На большинстве платформ Unix, где доступен системный вызов fork(), fork() в Perl просто вызывает его.
На некоторых платформах, таких как Windows, где системный вызов fork() недоступен, Perl можно сконфигурировать для эмуляции fork() на уровне интерпретатора. Хотя эмуляция разработана для максимальной совместимости с реальным fork() на уровне программы Perl, существуют важные различия, которые вытекают из того, что все псевдо-дочерние «процессы», созданные таким образом, живут в одном реальном процессе с точки зрения операционной системы.
Этот документ предоставляет общее описание возможностей и ограничений эмуляции fork(). Обратите внимание, что обсуждаемые здесь проблемы не применимы к платформам, где доступен реальный fork(), и Perl был сконфигурирован для его использования.
ОПИСАНИЕ
Эмуляция fork() реализуется на уровне интерпретатора Perl. Это означает, что вызов fork() фактически клонирует работающий интерпретатор и все его состояние, и выполняет клонированный интерпретатор в отдельном потоке, начав выполнение в новом потоке сразу после точки, где в родительском потоке был вызван fork(). Мы будем называть поток, реализующий этот дочерний «процесс», псевдопроцессом.
Для программы Perl, которая вызвала fork(), всё это должно быть прозрачным. Родитель возвращает из fork() с идентификатором псевдопроцесса, который впоследствии может быть использован в любых функциях управления процессами; дочерний процесс возвращает из fork() со значением 0 для обозначения того, что это дочерний псевдопроцесс.
Поведение других функций Perl в разветвлённых псевдопроцессах
Большинство функций Perl ведут себя естественным образом внутри псевдопроцессов.
- $$ или $PROCESS_ID
-
Эта специальная переменная правильно устанавливается в идентификатор псевдопроцесса. Она может быть использована для идентификации псевдопроцессов в рамках определённой сессии. Обратите внимание, что это значение может быть перезаписано, если после запуска одних псевдопроцессов другие были обработаны wait().
- %ENV
-
Каждый псевдопроцесс поддерживает свою виртуальную среду. Изменения в %ENV влияют на виртуальную среду и видны только внутри этого псевдопроцесса, а также в любых процессах (или псевдопроцессах), запущенных из него.
- chdir() и все другие встроенные функции, принимающие имена файлов
-
Каждый псевдопроцесс поддерживает своё виртуальное представление текущей директории. Изменения текущей директории с помощью chdir() видны только внутри этого псевдопроцесса и в любых процессах (или псевдопроцессах), запущенных из него. Все обращения к файлам и каталогам из псевдопроцесса корректно отображают виртуальную рабочую директорию в реальную рабочую директорию.
- wait() и waitpid()
-
wait() и waitpid() могут принимать идентификатор псевдопроцесса, возвращённый функцией fork(). Эти вызовы корректно ждут завершения псевдопроцесса и возвращают его состояние.
- kill()
-
kill('KILL', ...)может использоваться для завершения псевдопроцесса путём передачи ему ID, возвращённого fork(). Результат kill() для псевдопроцесса непредсказуем, и его не следует использовать, кроме крайних случаев, потому что операционная система может не гарантировать целостность ресурсов процесса при завершении работающего потока. Процесс, реализующий псевдопроцессы, может быть заблокирован, и интерпретатор Perl зависнет. Обратите внимание, что использованиеkill('KILL', ...)для псевдопроцесса может привести к утечкам памяти, поскольку поток, реализующий псевдопроцесс, не получает возможности очистить свои ресурсы.kill('TERM', ...)также может быть использовано для псевдопроцессов, но сигнал не будет доставлен, пока псевдопроцесс заблокирован системным вызовом, например, ожидая подключения сокета или пытаясь прочитать данные из сокета, если они недоступны. Начиная с Perl 5.14, родительский процесс не ждёт завершения дочерних процессов, получивших сигналkill('TERM', ...), чтобы избежать тупика при выходе из процесса. Вам необходимо явно вызвать waitpid(), чтобы убедиться, что у дочернего процесса есть время для очистки, но вы также несете ответственность за то, чтобы дочерний процесс не блокировался на ввода-выводе. - exec()
-
Вызов exec() внутри псевдопроцесса фактически запускает запрошенное исполняемое приложение в отдельном процессе и ждёт его завершения перед выходом со статусом завершения, равным статусу этого процесса. Это означает, что идентификатор процесса, сообщаемый внутри исполняемого приложения, будет отличаться от того, что мог вернуть ранний вызов Perl fork(). Аналогично, любые функции управления процессами, применённые к ID, возвращённому fork(), будут влиять на ожидающий псевдопроцесс, вызвавший exec(), а не на реальный процесс, которого он ждёт после exec().
Когда exec() вызывается внутри псевдопроцесса, методы DESTROY и блоки END всё равно будут вызваны после возвращения внешнего процесса.
- exit()
-
exit() всегда завершает только выполняющийся псевдопроцесс после автоматического ожидания завершения любых ожидающих дочерних псевдопроцессов. Обратите внимание, что это означает, что процесс в целом завершится только после завершения всех работающих псевдопроцессов. Ниже приведены некоторые ограничения, связанные с открытыми дескрипторами файлов.
- Открытые дескрипторы файлов, каталогов и сетевых сокетов
-
Все открытые дескрипторы дублируются в псевдопроцессах, так что закрытие любых дескрипторов в одном процессе не влияет на другие. Ниже приведены некоторые ограничения.
Ограничения ресурсов
С точки зрения операционной системы, псевдопроцессы, созданные с помощью эмуляции fork(), представляют собой просто потоки в одном процессе. Это означает, что любые ограничения на уровне процесса, наложенные операционной системой, применяются ко всем псевдопроцессам вместе. Это включает любые ограничения, наложенные операционной системой на количество открытых файлов, каталогов и сокетов, ограничения на использование дискового пространства, ограничения на размер памяти, ограничения на использование ЦП и т. д.
Уничтожение родительского процесса
Если родительский процесс уничтожается (либо с помощью встроенной функции Perl kill(), либо другими средствами), все псевдопроцессы также уничтожаются, и весь процесс завершается.
Срок жизни родительского процесса и псевдопроцессов
Во время обычного хода событий родительский процесс и каждый псевдопроцесс, запущенный им, будут ждать завершения своих соответствующих псевдодочерних процессов перед завершением. Это означает, что родительский процесс и каждый псевдодочерний процесс, являющийся также псевдородительским, завершатся только после завершения их псевдодочерних процессов.
Начиная с Perl 5.14, родительский процесс не будет автоматически ждать завершения любого дочернего процесса, которому был послан сигнал kill('TERM', ...), чтобы избежать тупика в случае, если дочерний процесс заблокирован на ввода-выводе и никогда не получает сигнал.
ОСОБЕННОСТИ И ОГРАНИЧЕНИЯ
- BEGIN блоки
-
Эмуляция fork() не будет работать корректно при вызове внутри блока BEGIN. Ответвленная копия выполнит содержимое блока BEGIN, но не продолжит парсить исходный поток после блока BEGIN. Например, рассмотрите следующий код:
BEGIN { fork and exit; # fork child and exit the parent print "inner\n"; } print "outer\n";Это выведет:
innerа не ожидаемое:
inner outerЭто ограничение обусловлено фундаментальными техническими трудностями при клонировании и перезапуске стеков, используемых Perl-парсером, в середине разбора.
- Открытые дескрипторы файлов
-
Любые открытые дескрипторы файлов во время fork() будут дублироваться (dup()). Таким образом, файлы могут быть закрыты независимо в родительском и дочернем процессе, но следует учитывать, что дублированные дескрипторы по-прежнему будут использовать один и тот же указатель позиции. Изменение позиции указателя в родительском процессе изменит его в дочернем и наоборот. Можно избежать этого, открывая файлы, которым нужны отдельные указатели позиции, в дочернем процессе отдельно.
В некоторых операционных системах, таких как Solaris и Unixware, вызов
exit()из дочернего процесса приведет к очистке и закрытию открытых дескрипторов файлов в родительском процессе, тем самым повреждая дескрипторы файлов. В этих системах рекомендуется вместо этого вызывать_exit()._exit()доступен в Perl через модульPOSIX. Обратитесь к руководству вашей системы для получения дополнительной информации об этом. - Открытые дескрипторы каталогов
-
Perl полностью прочитает все открытые дескрипторы каталогов до достижения конца потока. Затем он вернется в исходное положение с помощью seekdir() и все последующие запросы readdir() будут выполнены из кеш-буфера. Это означает, что ни дескриптор каталога, используемый родительским процессом, ни дескриптор, используемый дочерним процессом, не увидят никаких изменений, внесенных в каталог после вызова fork().
Обратите внимание, что rewinddir() имеет аналогичное ограничение в Windows и также не заставит readdir() снова прочитать каталог. Только новый открытый дескриптор каталога отразит изменения в каталоге.
- Forking pipe open() еще не реализовано
-
Конструкции
open(FOO, "|-")иopen(BAR, "-|")еще не реализованы. Это ограничение легко обойти в новом коде, создав явную трубу. Следующий пример демонстрирует запись в дочерний процесс:# simulate open(FOO, "|-") sub pipe_to_fork ($) { my $parent = shift; pipe my $child, $parent or die; my $pid = fork(); die "fork() failed: $!" unless defined $pid; if ($pid) { close $child; } else { close $parent; open(STDIN, "<&=" . fileno($child)) or die; } $pid; } if (pipe_to_fork('FOO')) { # parent print FOO "pipe_to_fork\n"; close FOO; } else { # child while (<STDIN>) { print; } exit(0); }А этот - чтение из дочернего процесса:
# simulate open(FOO, "-|") sub pipe_from_fork ($) { my $parent = shift; pipe $parent, my $child or die; my $pid = fork(); die "fork() failed: $!" unless defined $pid; if ($pid) { close $child; } else { close $parent; open(STDOUT, ">&=" . fileno($child)) or die; } $pid; } if (pipe_from_fork('BAR')) { # parent while (<BAR>) { print; } close BAR; } else { # child print "pipe_from_fork\n"; exit(0); }Конструкции форкирования pipe open() будут поддерживаться в будущем.
- Глобальное состояние, поддерживаемое XSUB
-
Внешие подпрограммы (XSUB), поддерживающие собственное глобальное состояние, могут работать некорректно. Таким XSUB необходимо поддерживать блокировки для защиты одновременного доступа к глобальным данным от разных псевдопроцессов или сохранять все состояние в таблице символов Perl, которая естественным образом копируется при вызове fork(). Механизм обратного вызова, предоставляющий расширениям возможность клонировать своё состояние, будет реализован в ближайшем будущем.
- Интерпретатор, встроенный в большее приложение
-
Эмуляция fork() может вести себя непредсказуемо при выполнении в приложении, встраивающем интерпретатор Perl и вызывающем Perl-API, способные оценивать фрагменты Perl-кода. Это обусловлено тем, что эмуляция знает только собственные структуры данных интерпретатора Perl и не знает ничего о состоянии содержащего приложения. Например, любое состояние, хранящееся в собственном стеке вызовов приложения, недоступно.
- Потокобезопасность расширений
-
Поскольку эмуляция fork() выполняет код в нескольких потоках, расширения, вызывающие не потокобезопасные библиотеки, могут работать ненадёжно при вызове fork(). По мере того как поддержка потоков Perl становится всё более широко принятой даже на платформах с родной поддержкой fork(), ожидается, что такие расширения будут исправлены для обеспечения потокобезопасности.
ОСОБЕННОСТИ ПЕРЕНОСИМОСТИ
В переносимом Perl-коде kill(9, $child) не должен использоваться в дочерних процессах. Уничтожение дочернего процесса небезопасно и имеет непредсказуемые последствия. См. "kill()", выше.
ОШИБКИ
-
Использование псевдо-ID процессов в виде отрицательных целых чисел приводит к ошибкам для целого числа
-1, так как функции wait() и waitpid() рассматривают это число как специальное. В текущей реализации молчаливо предполагается, что система никогда не выделяет идентификатор потока1для пользовательских потоков. В будущем будет реализована лучшая система представления идентификаторов псевдо-процессов. -
В некоторых случаях операторы pipe(), socket() и accept(), создающие системные дескрипторы, по-видимому, не дублируются точно в псевдопроцессах. Это происходит только в некоторых ситуациях, но в тех случаях, где это происходит, это может привести к тупиковым ситуациям (deadlocks) между конечными точками чтения и записи труб или невозможности отправки или получения данных через сокеты.
-
Этот документ может быть неполным в некоторых аспектах.
АВТОР
Поддержка одновременных интерпретаторов и эмуляции fork() была реализована ActiveState при финансовой поддержке Microsoft Corporation.
Этот документ написан и поддерживается Gurusamy Sarathy <gsar@activestate.com>.
СМОТРИТЕ ТАКЖЕ
© 1993–2020 Larry Wall and others
Licensed under the GNU General Public License version 1 or later, or the Artistic License.
The Perl logo is a trademark of the Perl Foundation.
https://perldoc.perl.org/5.28.3/perlfork