skip to content

In PHP, if you start two Fibers that each run a CPU-heavy loop or call sleep(), do they run in parallel, and why?

level: middleimportance: must knowfreq 38%

answer

  1. one thread, one fiber at a time
  2. switches only at explicit calls
  3. no time slicing in the engine
  4. sleep() stalls every fiber
  5. concurrency needs non-blocking I/O plus a loop

basics

~20 s

No. All fibers in a PHP process share one thread and only one runs at a time; control moves only when code calls Fiber::suspend(), start(), resume() or throw(). A CPU loop or sleep() inside one fiber stalls all the others.

solid answer

~40 s

No. Fibers give PHP **cooperative concurrency, not parallelism**. Every fiber in a process runs on the same thread, and exactly one of them, or the main script, executes at any instant. The engine never time-slices between fibers: a switch happens only when running code calls `Fiber::suspend()`, when the caller calls `start()`, `resume()` or `throw()`, or when a fiber finishes. So a CPU-bound loop runs to completion before anything else gets a turn, and a blocking call such as `sleep(1)`, a PDO query or `file_get_contents()` blocks the whole process, every fiber included. Two fibers each sleeping one second take two seconds. Fibers help only when a library turns waiting into suspension, using non-blocking streams and an event loop; CPU parallelism still needs more processes.

code

php · 18 lines
php
<?php
declare(strict_types=1);

$t0 = hrtime(true);

$fibers = [];
foreach (['a', 'b'] as $name) {
    $fibers[] = new Fiber(function () use ($name): void {
        sleep(1);                 // blocks the whole process
        echo "$name woke\n";
    });
}

foreach ($fibers as $fiber) {
    $fiber->start();              // runs a to completion, then b
}

printf("elapsed %.1f s\n", (hrtime(true) - $t0) / 1e9); // ~2.0, not ~1.0

go deeper

for a junior

Remember the headline: PHP fibers are not threads, only one runs at a time, and sleep() inside a fiber pauses everything.

for a middle

Explain the explicit switch points, the absence of preemption, and why only suspension-aware I/O lets other fibers run during a wait.

for a senior

Spot the production traps: a stray PDO call or CPU burst inside a fiber-based service stalls every connection, and CPU scaling must come from more processes.

for a principal

Decide whether an async fiber stack fits a workload at all; I/O fan-out gains, CPU-bound work does not, and the non-blocking client ecosystem constrains the choice.

## The short answer Fibers do **not** run in parallel. They are a way to **interleave** several tasks on a single thread, and the interleaving happens only where the code asks for it. Interviewers ask this because the word "fiber" is often heard as "lightweight thread", and the thread half of that picture is wrong for PHP. ## Why only one fiber runs at a time A PHP process (a CLI script, one PHP-FPM worker, one worker of a long-running server) executes PHP code on one thread. A `Fiber` adds a **separate call stack**, not a separate thread. Switching fibers means the engine saves one stack and swaps another in on that same thread. The switch points are all explicit calls: - `$fiber->start()`, `$fiber->resume()` and `$fiber->throw()` switch **into** a fiber; - the static `Fiber::suspend()` switches **out of** the running fiber, back to whoever started or resumed it; - a fiber that returns or throws also hands control back to that caller. There is **no preemption and no time slicing**: the engine has no timer that interrupts a busy fiber to let another one run. PHP ships only the switching primitive; there is no built-in scheduler that decides which fiber goes next. (Since PHP 8.4 the engine may run destructors triggered by the garbage collector in a fiber of its own, but it never hands the thread to another of your fibers.) ## What that means for CPU work and blocking calls | Work inside a fiber | What the other fibers experience | |---|---| | A tight CPU loop with no suspension | nothing runs until the loop ends | | `sleep()` or `usleep()` | the whole process sleeps | | A PDO or mysqli query | the whole process waits on the database | | `file_get_contents()` on a URL | the whole process waits on the network | | A library call that suspends while waiting | the others run while this one waits | The last row is the only case where fibers overlap anything, and what overlaps is **waiting**, not computing. ## Where the concurrency actually comes from Fibers become useful for I/O through event-loop libraries such as **Revolt** (which Amp is built on) and **ReactPHP**'s async package. Roughly: 1. The library sets its sockets and streams to non-blocking mode. 2. When a read or write would wait, the library registers interest with its event loop and calls `Fiber::suspend()` for the current fiber. 3. The loop waits on all registered streams at once and resumes each fiber when its stream is ready. The program still does one thing at a time; it just stops wasting the waits. Ten HTTP calls that each wait 200 ms can finish in roughly 200 ms total, but ten 200 ms CPU computations still take two seconds. ## Reading a fiber snippet quickly When an interviewer shows fiber code and asks what runs when, trace it in four steps: 1. Find every `start()`, `resume()` and `throw()` call: control enters a fiber only there. 2. Inside each fiber, find every `Fiber::suspend()`, direct or inside a library call: control leaves only there. 3. Treat everything between those points as one uninterrupted block, including any `sleep()`, query or CPU loop it contains. 4. Add up the blocking blocks: their sum is the wall-clock time, however many fibers are involved. ## Getting real parallelism in PHP If the work is CPU-bound, the answer is **more processes**, not more fibers: - more PHP-FPM or worker processes, each handling its own requests; - a job queue with several consumer processes; - forking or spawning child processes from a CLI script. Each process has its own memory and runs on its own core, which is what parallelism requires. ## Common wrong answers - "Fibers are PHP's threads." They share one thread; only the stacks differ. - "Fibers make blocking functions non-blocking." `sleep()`, PDO and cURL's blocking calls behave exactly as before; only code written to suspend can yield the thread. - "The engine schedules fibers fairly." There is no engine scheduler; a fiber that never suspends keeps the thread. In an interview, say it in one line: **fibers let PHP overlap waiting, never computing**.

  • Does using Fibers require a thread-safe (ZTS) build of PHP?
    No. `Fiber` is part of the engine on every PHP 8.1+ build, thread-safe or not. A fiber creates no thread; it only allocates a separate stack and swaps it in on the thread the script already runs on, so there is nothing for thread safety to protect.
  • How would you speed up a PHP CLI job that resizes thousands of images?
    That work is CPU-bound, so fibers cannot help. Split the batch across several processes: a queue with multiple consumer workers, or a parent script that spawns child processes, each taking a slice of the images. Each process runs on its own core with its own memory, giving the parallelism fibers cannot.
  • Does a Fiber-based event-loop library make a PDO query non-blocking?
    No. PDO's calls block the thread until the database answers, and a blocked thread cannot run the loop or any other fiber. Fiber-friendly libraries ship their own database clients that speak the wire protocol over non-blocking sockets and suspend while waiting; PDO itself has no such mode.

A single cook working through several recipes: the cook switches recipes only when one says wait for the oven. Chopping still happens one board at a time, so more recipes never mean faster chopping.

saying these in an interview costs you the question

  • Two fibers doing CPU work finish twice as fast
  • Each Fiber runs on its own thread, so fibers use several cores
  • PHP's engine preempts a busy fiber so others get a turn
  • Starting code in a Fiber makes sleep() or PDO non-blocking
  • Fibers need a thread-safe (ZTS) PHP build