PHP7
Asynchronous Programming
Multithreading
FastCGI Process Manager
Web Development

Async/Thread on PHP7 with FPM

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

PHP-FPM (FastCGI Process Manager) uses a process-based model where each request runs in a separate process with no shared memory. PHP does not support threads in FPM mode — the pthreads extension only works with the CLI SAPI, not with web server SAPIs. For async behavior in PHP-FPM, use non-blocking I/O libraries like ReactPHP or Amp, offload work to message queues (Redis, RabbitMQ), use exec() to spawn background processes, or leverage PHP 8.1+ Fibers for cooperative multitasking.

Why Threads Don't Work in FPM

php
1// This does NOT work under PHP-FPM
2// pthreads extension is CLI-only
3
4class MyThread extends Thread {
5    public function run() {
6        echo "Thread running";
7    }
8}
9
10$thread = new MyThread();
11$thread->start();  // Fatal error in FPM: Class 'Thread' not found

PHP-FPM spawns worker processes, not threads. Each request gets its own process. The pthreads extension is explicitly disabled for web SAPIs because PHP's core data structures are not thread-safe.

Solution 1: ReactPHP (Event-Driven I/O)

ReactPHP provides an event loop for non-blocking I/O:

bash
composer require react/http react/promise
php
1use React\EventLoop\Factory;
2use React\Http\Browser;
3
4$loop = Factory::create();
5$client = new Browser($loop);
6
7// Fire multiple HTTP requests concurrently
8$promises = [
9    $client->get('https://api.example.com/users'),
10    $client->get('https://api.example.com/orders'),
11    $client->get('https://api.example.com/products'),
12];
13
14\React\Promise\all($promises)->then(function (array $responses) {
15    foreach ($responses as $response) {
16        echo $response->getBody() . "\n";
17    }
18});
19
20$loop->run();

All three requests run concurrently on a single process using non-blocking sockets. The event loop handles I/O multiplexing.

Solution 2: Amp (Coroutines)

Amp provides async/await style programming for PHP:

bash
composer require amphp/http-client amphp/amp
php
1use Amp\Http\Client\HttpClientBuilder;
2use Amp\Http\Client\Request;
3use function Amp\async;
4use function Amp\Future\await;
5
6$client = HttpClientBuilder::buildDefault();
7
8// Run requests concurrently using async/await
9$futures = [
10    async(fn() => $client->request(new Request('https://api.example.com/users'))),
11    async(fn() => $client->request(new Request('https://api.example.com/orders'))),
12];
13
14$responses = await($futures);
15
16foreach ($responses as $response) {
17    echo $response->getBody()->buffer() . "\n";
18}

Solution 3: Background Process with exec()

Fire-and-forget a background job from an FPM request:

php
1// Spawn a background PHP process
2exec('php /path/to/worker.php --job=send-email [email protected] > /dev/null 2>&1 &');
3
4// Return immediately to the client
5echo json_encode(['status' => 'queued']);
php
1// worker.php — runs independently of the web request
2$options = getopt('', ['job:', 'to:']);
3
4switch ($options['job']) {
5    case 'send-email':
6        sendEmail($options['to']);
7        break;
8}

The & at the end makes the process run in the background. The web request returns immediately while the worker runs independently.

Use a message broker to decouple web requests from background processing:

php
1// Web request: push job to Redis queue
2$redis = new Redis();
3$redis->connect('127.0.0.1');
4$redis->lPush('email_queue', json_encode([
5    'to' => '[email protected]',
6    'subject' => 'Welcome',
7    'body' => 'Hello!'
8]));
9
10echo json_encode(['status' => 'queued']);
php
1// Worker (runs as a separate long-running CLI process)
2$redis = new Redis();
3$redis->connect('127.0.0.1');
4
5while (true) {
6    $job = $redis->brPop('email_queue', 30);  // Block for 30s
7    if ($job) {
8        $data = json_decode($job[1], true);
9        sendEmail($data['to'], $data['subject'], $data['body']);
10    }
11}

Run the worker with supervisord or systemd to keep it alive:

ini
1# /etc/supervisor/conf.d/email-worker.conf
2[program:email-worker]
3command=php /path/to/worker.php
4numprocs=4
5autostart=true
6autorestart=true

Solution 5: PHP 8.1 Fibers

Fibers provide cooperative multitasking within a single process:

php
1// PHP 8.1+
2$fiber1 = new Fiber(function () {
3    echo "Fiber 1: start\n";
4    Fiber::suspend('fiber1-data');
5    echo "Fiber 1: resumed\n";
6});
7
8$fiber2 = new Fiber(function () {
9    echo "Fiber 2: start\n";
10    Fiber::suspend('fiber2-data');
11    echo "Fiber 2: resumed\n";
12});
13
14// Interleave execution
15$result1 = $fiber1->start();   // "Fiber 1: start"
16$result2 = $fiber2->start();   // "Fiber 2: start"
17$fiber1->resume();              // "Fiber 1: resumed"
18$fiber2->resume();              // "Fiber 2: resumed"

Fibers are the foundation for async libraries like Amp v3 and RevoltPHP. They allow cooperative scheduling but are not true threads — they run on a single OS thread.

Solution 6: curl_multi for Parallel HTTP

php
1$urls = [
2    'https://api.example.com/users',
3    'https://api.example.com/orders',
4    'https://api.example.com/products',
5];
6
7$multi = curl_multi_init();
8$handles = [];
9
10foreach ($urls as $url) {
11    $ch = curl_init($url);
12    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
13    curl_multi_add_handle($multi, $ch);
14    $handles[] = $ch;
15}
16
17// Execute all requests concurrently
18do {
19    curl_multi_exec($multi, $running);
20    curl_multi_select($multi);
21} while ($running > 0);
22
23// Collect results
24foreach ($handles as $ch) {
25    echo curl_multi_getcontent($ch) . "\n";
26    curl_multi_remove_handle($multi, $ch);
27    curl_close($ch);
28}
29
30curl_multi_close($multi);

curl_multi runs HTTP requests in parallel using non-blocking I/O, without requiring any external libraries.

Common Pitfalls

  • Trying to use pthreads in FPM: The pthreads extension only works with the CLI SAPI. It is explicitly incompatible with FPM, Apache mod_php, and other web server SAPIs. There is no workaround — PHP's core is not thread-safe under these SAPIs.
  • Using exec() without output redirection: exec('php worker.php') without > /dev/null 2>&1 & blocks until the worker finishes, making the web request wait. Always redirect output and background the process.
  • Not handling queue worker failures: If a queue worker crashes, messages are lost unless the queue supports acknowledgments. Use RabbitMQ with manual acks or Redis with BRPOPLPUSH for reliable processing.
  • Running event loop libraries inside FPM requests: ReactPHP and Amp event loops are designed for long-running CLI processes. Running them inside a short-lived FPM request adds overhead and complexity. Use them for CLI workers, not web requests.
  • Confusing Fibers with threads: Fibers are cooperative (they explicitly yield), run on a single OS thread, and do not provide parallelism. They enable async-style code but cannot use multiple CPU cores. For CPU-bound parallelism, use multiple worker processes.

Summary

  • PHP-FPM does not support threads — pthreads is CLI-only
  • Use curl_multi for parallel HTTP requests within a single FPM request
  • Use ReactPHP or Amp for event-driven async I/O in CLI workers
  • Offload background work to message queues (Redis, RabbitMQ) with separate worker processes
  • PHP 8.1 Fibers enable cooperative multitasking but are not true threads
  • For production async workloads, use message queues with supervisor-managed workers

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.