5 Best Ways to Create Child Processes Using Fork in Python

Understanding the Fork() System Call in Python for Child Process Creation

πŸ’‘ Problem Formulation: Creating child processes in an operating system is a fundamental task that allows for concurrent execution of tasks. In Python, creating a child process can be achieved using the fork() system call available on Unix-based systems. The input is a Python function or task that requires separate execution, and the desired output is the successful creation and running of this task in a child process while the parent process continues or waits.

Method 1: Basic Forking

The os.fork() method in Python is a direct interface to the Unix fork system call. It creates a child process that is a duplicate of the parent process. Each process has its own memory space and if the fork is successful, the ‘PID’ (Process ID) of the child process will be returned in the parent, while ‘0’ is returned in the child.

Here’s an example:

import os

def child_process():
    print("I am the child process and my PID is: ", os.getpid())

def parent_process(child_pid):
    print("I am the parent process with PID: ", os.getpid(), " and my child's PID is: ", child_pid)

pid = os.fork()

if pid == 0:
    child_process()
else: 
    parent_process(pid)

Output:

I am the parent process with PID:  1234  and my child's PID is:  1235
I am the child process and my PID is:  1235

This code snippet uses os.fork() to create a child process. In the child process, where pid equals zero, we call child_process(). In the parent, the pid variable contains the process ID of the child, so we call parent_process(pid). Each process prints its own message to the terminal with its PID.

Method 2: Forking with Custom Execution

In this approach, the child process executes a different program from the parent using the os.execvp() function. This replaces the current running process with a new program.

Here’s an example:

import os

pid = os.fork()

if pid > 0:
    print("This is the parent process. PID:", os.getpid())
else:
    print("This is the child process. PID:", os.getpid())
    os.execvp("python", ["python", "-c", "print('Executing as a different program')"])

Output:

This is the parent process. PID: 1234
Executing as a different program

After forking, in the child block (where pid equals zero), the os.execvp() function is called with arguments to execute a simple Python command that prints a message. Parent process prints its PID.

Method 3: Forking with Process Monitoring

Using os.wait(), the parent process can wait for completion of the child process. This is necessary when the parent’s subsequent actions depend on the result of the child process.

Here’s an example:

import os

pid = os.fork()

if pid > 0:
    print("Parent PID:", os.getpid())
    os.wait()
    print("Parent process is done waiting.")
else:
    print("Child PID:", os.getpid())
    print("Child process is exiting.")

Output:

Parent PID: 1234
Child PID: 1235
Child process is exiting.
Parent process is done waiting.

The parent process prints its PID, then waits for the child process to complete before it prints its final message. The child simply prints its PID, then a message before it exits.

Method 4: Forking and Inter-Process Communication (IPC)

Through IPC, processes can communicate with each other. os.pipe() creates a pipe that can be used for IPC. The parent writes to the pipe, and the child reads from it.

Here’s an example:

import os

r, w = os.pipe() 

pid = os.fork()

if pid > 0:
    os.close(r)
    w = os.fdopen(w, 'w')
    print("Parent writing")
    w.write("Parent was here")
    w.close()
else:
    os.close(w)
    r = os.fdopen(r)
    print("Child reading")
    print(r.read())
    r.close()

Output:

Parent writing
Child reading
Parent was here

The code defines a pipe, then forks. The parent closes the read descriptor, writes a message, and closes the write descriptor. The child closes the write descriptor, reads the message, prints it, and closes the read descriptor.

Bonus One-Liner Method 5: Forking with Function

Simplify forking by wrapping the process creation and management into a function that accepts another function to run in the child process.

Here’s an example:

import os

def run_in_child(func):
    if os.fork() == 0:
        func()
        os._exit(0)

run_in_child(lambda: print("Running in child, PID:", os.getpid()))

Output:

Running in child, PID: 1235

The function run_in_child() takes another function as its argument, forks, and if in the child process, runs the given function. It then exits the child process immediately to avoid running anything else inadvertently.

Summary/Discussion

Method 1: Basic Forking. Strengths: Simple and straightforward. Weaknesses: Limited flexibility for execution in the child process.

Method 2: Forking with Custom Execution. Strengths: Allows running a completely different program in the child. Weaknesses: Replaces the current process entirely.

Method 3: Forking with Process Monitoring. Strengths: Enables synchronizing the parent and child. Weaknesses: The parent process is blocked until the child finishes.

Method 4: Forking and Inter-Process Communication (IPC). Strengths: Enables direct communication between processes. Weaknesses: Requires careful resource management to avoid leaks.

Method 5: Bonus One-Liner. Strengths: Simplifies the process of creating a child process. Weaknesses: Not as explicit, which could be less readable for some.