Сообщить об ошибке.

Функция fork() модуля os в Python

Создание дочернего процесса

Синтаксис:

import os

os.fork()

Параметры:

  • Нет

Описание:

Функция fork() модуля os создает клон текущего процесса как дочерний процесс. Возвращает 0 в дочернем процессе и идентификатор дочернего процесса в родительском элементе. Если возникает ошибка, то возникает исключение OSError.

Обратите внимание, что некоторые платформы, включая FreeBSD <= 6.3 и Cygwin, имеют известные проблемы при использовании os.fork() из потока.

Вызывает событие аудита os.fork без аргументов.

Изменено в версии Python 3.8: вызов os.fork() в субинтерпретаторе больше не поддерживается (возникает ошибка RuntimeError).

Изменено в версии Python 3.12: если Python обнаруживает, что процесс имеет несколько потоков, то os.fork() теперь выдает предупреждение об устаревании DeprecationWarning.

Чтобы лучше информировать разработчиков о проблеме проектирования, которую платформа POSIX специально отмечает как не поддерживаемую, команда разработчиков Python решила отображать это как предупреждение. Даже в коде, который, кажется, что работает, на платформах POSIX небезопасно смешивать многопоточность с os.fork(). Сама среда выполнения CPython всегда выполняла вызовы API, которые небезопасны для использования в дочернем процессе, когда потоки существовали в родительском (например, malloc и free).

Пользователи macOS или пользователи реализаций libc или malloc, отличных от тех, которые обычно встречаются в glibc на сегодняшний день, относятся к числу тех, кто уже с большей вероятностью столкнется с взаимоблокировками при запуске такого кода.

Доступность: POSIX, но недоступно на Emscripten и WASI.

Примеры создания дочерних процессов

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

# test_fork.py
import os

pid = os.fork()

if pid:
    print('ID child process:', pid)
else:
    print('I am the child')

# $ python3 test_fork.py
# ('Child process id:', 9486)
# I am the child

Родитель может отправлять сигналы дочернему процессу, используя функцию os.kill() используя модуль signal. В этом примере используется короткая пауза, чтобы дать дочернему процессу время для настройки обработчика сигнала. Реальное приложение, не будет нуждаться в вызове time.sleep(). В дочернем процессе установим обработчик сигнала и сделаем небольшую паузу, чтобы предоставить родительскому процессу достаточно времени для отправки сигнала.

# test_kill.py
import os
import signal
import time

def signal_usr1(signum, frame):
    "Обратный вызов вызывается при получении сигнала"
    pid = os.getpid()
    print(f'Получен USR1 в процессе {pid}')

print('Forking...')
child_pid = os.fork()
if child_pid:
    print('PARENT: Пауза перед отправкой сигнала ...')
    time.sleep(1)
    print(f'PARENT: передача сигналов {child_pid}')
    os.kill(child_pid, signal.SIGUSR1)
else:
    print('CHILD: Настройка обработчика сигнала')
    signal.signal(signal.SIGUSR1, signal_usr1)
    print('CHILD: Пауза в ожидании сигнала')
    time.sleep(5)

# $ python3 test_kill.py
# Forking...
# PARENT: Пауза перед отправкой сигнала ...
# CHILD: Настройка обработчика сигнала
# CHILD: Пауза в ожидании сигнала
# PARENT: передача сигналов 21168
# Получен USR1 в процессе 21168

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

# test_exec.py
import os

child_pid = os.fork()
if child_pid:
    os.waitpid(child_pid, 0)
else:
    os.execlp('pwd', 'pwd', '-P')

# $ python3 test_exec.py
# /home/docs-python

Когда программа запускается функцией os.exec(), код из этой программы заменяет код из существующего процесса.

Примеры ожидания дочерних процессов

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

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

# test_wait.py
import os
import sys
import time

for i in range(2):
    print(f'PARENT {os.getpid()}: Forking {i}')
    worker_pid = os.fork()
    if not worker_pid:
        print(f'WORKER {i}: Starting')
        time.sleep(2 + i)
        print(f'WORKER {i}: Finishing')
        sys.exit(i)

for i in range(2):
    print(f'PARENT: Waiting for {i}')
    done = os.wait()
    print(f'PARENT: Child done: {done}')

# $ python3 test_wait.py 
# PARENT 24512: Forking 0
# PARENT 24512: Forking 1
# WORKER 0: Starting
# PARENT: Waiting for 0
# WORKER 1: Starting
# WORKER 0: Finishing
# PARENT: Child done: (24513, 0)
# PARENT: Waiting for 1
# WORKER 1: Finishing
# PARENT: Child done: (24514, 256)

Функция os.wait() возвращает идентификатор процесса и код завершения, упакованный в 16-битовое значение. Младший байт представляет номер сигнала, прекратившего выполнение процесса, а старший - код состояния, возвращенный процессом по его завершении.

Чтобы дождаться определенного процесса, используйте функцию os.waitpid().

Функция os.waitpid(), которой передан идентификатор целевого процесса, будет блокироваться до тех пор, пока процесс не завершится.

# test_waitpid.py
import os
import sys
import time

workers = []
for i in range(2):
    print(f'PARENT {os.getpid()}: Forking {i}')
    worker_pid = os.fork()
    if not worker_pid:
        print(f'WORKER {i}: Starting')
        time.sleep(2 + i)
        print(f'WORKER {i}: Finishing')
        sys.exit(i)
    workers.append(worker_pid)

for pid in workers:
    print(f'PARENT: Waiting for {pid}')
    done = os.waitpid(pid, 0)
    print(f'PARENT: Child done: {done}')

# $ python3 test_waitpid.py 
# PARENT 25144: Forking 0
# PARENT 25144: Forking 1
# WORKER 0: Starting
# PARENT: Waiting for 25145
# WORKER 1: Starting
# WORKER 0: Finishing
# PARENT: Child done: (25145, 0)
# PARENT: Waiting for 25146
# WORKER 1: Finishing
# PARENT: Child done: (25146, 256)