In PHP's pcntl extension, what does pcntl_fork() return, and how do the parent and the child tell which one they are?
answer
- one call, two processes return
- positive integer in one, zero in other
- -1 plus an E_WARNING on failure
- posix_getpid() and posix_getppid()
- child must exit() itself
basics
~20 spcntl_fork() returns the new child's PID in the parent, 0 in the child, and -1 in the parent if the fork failed. Both processes continue from the line after the call, each with its own copy of every variable.
solid answer
~40 s`pcntl_fork()` duplicates the running CLI process and returns in both copies. The parent gets the child's PID (a positive `int`), the child gets `0`, and on failure only the parent exists and gets `-1` with an `E_WARNING` such as "Reached the maximum limit of number of processes". So you branch on the return value: `-1` handle the error, `0` do the child's work and call `exit()`, anything else remember the PID so you can reap it later. The child finds its own PID with `posix_getpid()` and its parent's with `posix_getppid()`. Forgetting `exit()` in the child is the classic bug: the child falls through into the parent's code and may fork again. The extension is CLI-only and absent on Windows.
code
php · 21 lines<?php
declare(strict_types=1);
$children = [];
foreach (['a', 'b', 'c'] as $task) {
$pid = pcntl_fork();
if ($pid === -1) {
fwrite(STDERR, 'fork failed: ' . pcntl_strerror(pcntl_get_last_error()) . PHP_EOL);
exit(1);
}
if ($pid === 0) {
// child: do one task, then leave so the loop does not continue here
printf("task %s in pid %d (parent %d)\n", $task, posix_getpid(), posix_getppid());
exit(0);
}
$children[] = $pid; // parent: remember the PID
}
foreach ($children as $pid) {
pcntl_waitpid($pid, $status);
}go deeper
Remember the three return values: child PID in the parent, 0 in the child, -1 on failure. Then remember that the child must call exit() when its work is done.
Explain that both processes continue from the same line with copies of memory, and show how the child uses posix_getpid() and posix_getppid() to identify itself and its parent.
Point out what is copied and what is shared: variables are copies, but file descriptors such as sockets and database connections are the same objects in both processes, which is where forked workers break.
Weigh whether forking from PHP is the right tool at all versus separate processes started by a supervisor, given pcntl is CLI-only, Unix-only and must be explicitly compiled in.
## What `pcntl_fork()` does `pcntl_fork()` is PHP's wrapper around the Unix `fork()` system call, provided by the **pcntl** (process control) extension. It takes no arguments and has the signature `pcntl_fork(): int` in `ext/pcntl/pcntl.stub.php`. When it succeeds, the operating system creates a second process that is an almost exact copy of the first: the same script, the same position in the script, and a copy of every variable, object and open file descriptor. The unusual part is that **one call returns twice** — once in each process. The only way the code can tell which copy it is running in is the return value. ## The three possible return values | Return value | Where you are | What it means | |---|---|---| | a positive `int` | the parent | the fork worked; the value is the child's process ID (PID) | | `0` | the child | you are the new process | | `-1` | the parent | the fork failed, no child exists, and PHP raised an `E_WARNING` | The warning text on failure depends on the reason the operating system gave. The pinned source prints, for example on Linux, `Error 11: Reached the maximum limit of number of processes` when the per-user process limit is hit, or `Insufficient memory` when the kernel could not allocate the copy. The last error number is also stored and can be read with `pcntl_get_last_error()` and turned into text with `pcntl_strerror()`. Note that the parent does **not** get `false` on failure: the return type is `int`, so the check is `=== -1`, not `=== false`. ## Branching on the result The standard shape is a three-way branch: 1. `$pid === -1` — log the failure and decide whether to retry or stop. 2. `$pid === 0` — run the child's work, then call `exit()` with a status code. 3. otherwise — you are the parent; store `$pid` so you can wait for that child later. The `exit()` in the child branch is not optional. Without it the child finishes its work, leaves the `if` block and keeps executing whatever the parent would execute next. If the fork sits inside a loop, the child starts forking children of its own, and the process count grows geometrically. ## Identifying yourself after the fork The return value tells the parent who the child is, but the child receives only `0`. Two functions from the **posix** extension fill the gap: - `posix_getpid(): int` returns the calling process's own PID — in the child this is the same number the parent received from `pcntl_fork()`. - `posix_getppid(): int` returns the parent's PID, which a child can use to check whether its parent is still the process that started it. - `getmypid()` from the standard library also reads the current PID, so code without the posix extension can still log it. ## What the child inherits The child starts with **copies** of the parent's state, not shared access to it: - every PHP variable and object: changing `$counter` in the child does not change it in the parent; - open file descriptors, including sockets and database connections, which are the same underlying descriptors and are therefore genuinely shared; - installed `pcntl_signal()` handlers; - the output buffer, so text buffered with `ob_start()` before the fork can be printed twice. That split between copied memory and shared descriptors is where most real bugs with forking come from. ## Where it works - **CLI only.** The build system compiles pcntl into the CLI and CGI binaries (`--enable-pcntl`, described as "CLI/CGI only"), and the manual says process control should not be enabled in a web server environment. Forking a PHP-FPM worker in the middle of a request is not a supported design. - **Not on Windows.** The extension does not exist there. - **Must be enabled.** It is not part of a default build, so a portable script checks `extension_loaded('pcntl')` or `function_exists('pcntl_fork')` first. ## A short example ```php <?php $pid = pcntl_fork(); if ($pid === -1) { exit(1); } if ($pid === 0) { echo 'child ', posix_getpid(), PHP_EOL; exit(0); } pcntl_waitpid($pid, $status); ``` The parent's final call waits for the child to finish; reaping children properly is its own topic, but a parent should never simply forget the PIDs it created.
- What happens if the child branch has no exit() and the fork is inside a foreach loop?The child finishes its work and continues the loop exactly as the parent would, so it calls `pcntl_fork()` on the next iteration and creates grandchildren. Each of those does the same, so three iterations can produce up to eight processes instead of four. Ending the child branch with `exit()` is what keeps one fork equal to one worker.
- Can a PHP-FPM request call pcntl_fork() to run a slow task in the background?No, not as a design. pcntl is intended for CLI scripts; a static build only compiles it into CLI and CGI, and the manual warns that using it inside a web server gives unexpected results. A forked FPM worker would share the request's socket and output. Hand slow work to a queue consumed by a CLI worker instead.
saying these in an interview costs you the question
- pcntl_fork() returns false when the fork fails
- The child receives its own PID from pcntl_fork()
- The child shares the parent's variables, so changes are visible to both
- Only the child continues after the call; the parent waits
- pcntl_fork() works the same inside a PHP-FPM web request