Spec-Zone.ru › Perl 5.38

perlfork

СОДЕРЖАНИЕ

  • ИМЯ
  • СИНОПСИС
  • ОПИСАНИЕ
    • Поведение других функций Perl в порождённых псевдопроцессах
    • Ограничения ресурсов
    • Завершение родительского процесса
    • Жизненный цикл родительского процесса и псевдопроцессов
  • ЗАМЕЧАНИЯ И ОГРАНИЧЕНИЯ
  • ЗАМЕЧАНИЯ О ПЕРЕНОСИМОСТИ
  • ОШИБКИ
  • АВТОР
  • СМОТРИТЕ ТАКЖЕ

ИМЯ

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(), Perl's fork() просто вызывает её.

На некоторых платформах, таких как 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', ...) может использоваться для завершения псевдопроцесса, передав ему идентификатор, возвращённый функцией fork(). Результат kill() для псевдопроцесса непредсказуем, и его не следует использовать, кроме как в крайних случаях, потому что операционная система не гарантирует целостность ресурсов процесса при завершении работающего потока. Процесс, реализующий псевдопроцессы, может заблокироваться, и интерпретатор Perl зависнет. Обратите внимание, что использование kill('KILL', ...) для псевдопроцесса может привести к утечкам памяти, поскольку поток, реализующий псевдопроцесс, не получает возможности очистить свои ресурсы.

kill('TERM', ...) также может использоваться для псевдопроцессов, но сигнал не будет доставлен, пока псевдопроцесс заблокирован системным вызовом, например, ожидая подключения сокета или пытаясь прочитать данные из сокета без доступных данных. Начиная с Perl 5.14, родительский процесс не будет ожидать завершения дочерних процессов, которым был послан сигнал kill('TERM', ...), чтобы избежать тупика при завершении процесса. Вам придётся явно вызвать waitpid(), чтобы убедиться, что дочерний процесс успел завершить свои действия, но вы также должны убедиться, что дочерний процесс не заблокирован в ожидании ввода-вывода.

exec()

Вызов exec() в псевдопроцессе фактически запускает запрошенное исполняемое приложение в отдельном процессе и ожидает его завершения перед завершением с таким же выходным статусом, как и у этого процесса. Это означает, что идентификатор процесса, сообщённый внутри запущенного исполняемого файла, будет отличаться от того, что ранее могла вернуть функция Perl fork(). Аналогично, любые функции управления процессами, применённые к идентификатору, возвращённому функцией fork(), повлияют на ожидание псевдопроцесса, который вызвал exec(), а не на реальный процесс, который он ожидает после exec().

Когда exec() вызывается внутри псевдопроцесса, методы DESTROY и блоки END всё равно будут вызваны после возвращения внешнего процесса.

exit()

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

Открытые дескрипторы файлов, каталогов и сетевых сокетов

Все открытые дескрипторы дублируются в псевдопроцессах, так что закрытие любых дескрипторов в одном процессе не влияет на другие. Смотрите ниже некоторые ограничения.

Ограничения ресурсов

С точки зрения операционной системы, псевдопроцессы, созданные с помощью эмуляции 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, "-|") еще не реализованы. Это ограничение легко обойти в новом коде, создав pipe явно. Следующий пример показывает, как записать в дочерний процесс:

# 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);
}

Конструкции forking pipe open() будут поддерживаться в будущем.

Глобальное состояние, поддерживаемое XSUB

Внешние подпрограммы (XSUB), которые поддерживают собственное глобальное состояние, могут работать некорректно. Таким XSUB потребуется поддерживать блокировки для защиты одновременного доступа к глобальным данным от различных псевдопроцессов или поддерживать все свое состояние в таблице символов Perl, которая естественно копируется при вызове fork(). В ближайшем будущем будет предоставлен механизм обратного вызова, который предоставит расширениям возможность клонировать свое состояние.

Интерпретатор, встроенный в большее приложение

Эмуляция fork() может вести себя непредсказуемо, когда она выполняется в приложении, которое встраивает интерпретатор Perl и вызывает API Perl, которые могут оценивать фрагменты Perl-кода. Это связано с тем, что эмуляция знает только о собственных структурах данных интерпретатора Perl и ничего не знает о состоянии содержащего приложения. Например, любое состояние, передаваемое в собственном стеке вызовов приложения, недоступно.

Потокобезопасность расширений

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

ОСОБЕННОСТИ ПЕРЕНОСИМОСТИ

В переносимом Perl-коде kill(9, $child) не должно использоваться в дочерних процессах. Уничтожение дочернего процесса небезопасно и имеет непредсказуемые результаты. См. "kill()", выше.

ОШИБКИ

  • Использование псевдо-ID процессов как отрицательных целых чисел приводит к проблемам для целых чисел -1, так как функции wait() и waitpid() обрабатывают это число как специальное. В текущей реализации подразумевается, что система никогда не выделяет идентификатор потока 1 для пользовательских потоков. В будущем будет реализована лучшая система представления псевдо-ID процессов.

  • В определённых случаях операционные дескрипторы, созданные операторами pipe(), socket() и accept(), по-видимому, не дублируются корректно в псевдопроцессах. Это происходит только в некоторых ситуациях, но когда это происходит, это может привести к тупиковым ситуациям (deadlocks) между сторонами чтения и записи канала pipe или невозможности отправки или получения данных через сокет.

  • Этот документ может быть неполным в некоторых отношениях.

АВТОР

Поддержка одновременных интерпретаторов и эмуляции fork() была реализована ActiveState при финансировании Microsoft Corporation.

Этот документ написан и поддерживается Gurusamy Sarathy <gsar@activestate.com>.

СМОТРИТЕ ТАКЖЕ

"fork" в perlfunc, perlipc

© 1993–2023 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.38.0/perlfork

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API