Spec-Zone.ru › Perl 5.36

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() со значением идентификатора псевдопроцесса, который затем может быть использован в любой функции управления процессами; дочерний возвращает значение 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);
}

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

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

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

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

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

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

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

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

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

ОШИБКИ

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

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

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

АВТОР

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

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

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

"fork" в perlfunc, perlipc

© 1993–2021 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.36.0/perlfork

Spec-Zone.ru

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