Announcement

👇Official Account👇

Welcome to join the group & private message

Article first/tail QR code

Skip to content

PHP Process and Thread Analysis: Deep Dive into Concurrency Models

Series Navigation: This article is part of the PHP Performance Optimization series focusing on process management, concurrency, and production best practices.

Table of Contents


1. Introduction

PHP's process and thread model is fundamentally different from languages like Golang or Node.js. Understanding these differences is crucial for building high-performance PHP applications in 2025.

Why This Matters:

  • PHP-FPM powers millions of production websites
  • Process management directly impacts application performance
  • Thread safety affects extension choices and deployment strategies
  • Concurrency patterns differ significantly from modern languages

What You'll Learn:

  • ✅ PHP process architecture (CGI, FastCGI, PHP-FPM)
  • ✅ Multi-process programming with pcntl extension
  • ✅ Thread safety concepts (ZTS vs NTS)
  • ✅ Production-grade PHP-FPM optimization
  • ✅ Comparison with Go, Node.js, and Python concurrency models

2. PHP Process Model

2.1 Evolution: CGI → FastCGI → PHP-FPM

Traditional CGI (Common Gateway Interface)

mermaid
sequenceDiagram
    participant Client
    participant WebServer
    participant CGI Process
    
    Client->>WebServer: HTTP Request
    WebServer->>CGI Process: Fork new process
    CGI Process->>CGI Process: Load PHP interpreter
    CGI Process->>CGI Process: Parse PHP script
    CGI Process->>WebServer: Response
    WebServer->>Client: HTTP Response
    CGI Process->>CGI Process: Process terminates

Problems with CGI:

  • High overhead: New process for every request
  • Slow startup: PHP interpreter reload each time
  • Resource waste: Process creation/destruction cost
  • No connection pooling: Database connections can't be reused

FastCGI Improvements

php
// FastCGI keeps processes alive
while (FCGI_Accept() >= 0) {
    // Persistent process handles multiple requests
    $request = parse_request();
    $response = handle_request($request);
    send_response($response);
    // Process continues running
}

FastCGI Benefits:

  • Process reuse: Eliminate startup overhead
  • Persistent connections: Database connection pooling
  • Better performance: 10-100x faster than CGI
  • Separation of concerns: Web server and PHP runtime decoupled

2.2 PHP-FPM (FastCGI Process Manager)

PHP-FPM is the de facto standard for running PHP in production. It provides advanced process management features.

Architecture Overview

┌─────────────────────────────────────────────────┐
│              Nginx/Apache                       │
│  (Reverse Proxy + Static File Server)          │
└────────────────┬────────────────────────────────┘
                 │ Unix Socket / TCP:9000

┌─────────────────────────────────────────────────┐
│         PHP-FPM Master Process                  │
│  • Process Pool Management                      │
│  • Signal Handling (SIGINT, SIGTERM, SIGUSR1)  │
│  • Configuration Reload                         │
│  • Worker Process Monitoring                    │
└────────────────┬────────────────────────────────┘

        ┌────────┴────────┬────────┬────────┐
        ↓                 ↓        ↓        ↓
    Worker 1          Worker 2  Worker 3  Worker N
    (Idle)            (Busy)    (Busy)    (Idle)
    │                   │         │         │
    └───────────────────┴─────────┴─────────┘
            Handle PHP Requests

Master-Worker Pattern

php
// Simplified PHP-FPM Master Process Logic (Conceptual)
class PHPFPMMaster {
    private array $workers = [];
    private array $config;
    
    public function start(): void {
        // 1. Load configuration
        $this->loadConfig();
        
        // 2. Daemonize process
        $this->daemonize();
        
        // 3. Create initial worker pool
        $this->spawnWorkers($this->config['start_servers']);
        
        // 4. Signal handling
        pcntl_signal(SIGINT, [$this, 'handleShutdown']);
        pcntl_signal(SIGUSR1, [$this, 'reloadConfig']);
        
        // 5. Monitor loop
        while (true) {
            $this->checkWorkerHealth();
            $this->adjustWorkerPool();
            pcntl_signal_dispatch();
            sleep(1);
        }
    }
    
    private function spawnWorkers(int $count): void {
        for ($i = 0; $i < $count; $i++) {
            $pid = pcntl_fork();
            
            if ($pid === 0) {
                // Child process - become worker
                $this->becomeWorker();
                exit;
            } else {
                // Master process - track worker
                $this->workers[$pid] = [
                    'status' => 'idle',
                    'requests' => 0,
                    'start_time' => time()
                ];
            }
        }
    }
    
    private function adjustWorkerPool(): void {
        $idleWorkers = $this->countIdleWorkers();
        $busyWorkers = count($this->workers) - $idleWorkers;
        
        // Dynamic scaling based on load
        if ($idleWorkers < $this->config['min_spare_servers']) {
            $this->spawnWorkers(1);
        } elseif ($idleWorkers > $this->config['max_spare_servers']) {
            $this->terminateIdleWorker();
        }
    }
}

2.3 PHP-FPM Configuration Deep Dive

Essential Configuration Options

ini
; /etc/php/8.3/fpm/pool.d/www.conf

[www]
; User/Group
user = www-data
group = www-data

; Listen Configuration
; Option 1: Unix Socket (Faster, local only)
listen = /run/php/php8.3-fpm.sock
; Option 2: TCP Socket (Network accessible)
; listen = 127.0.0.1:9000

; Process Management Strategy
pm = dynamic                    ; dynamic | static | ondemand

; Static Mode: Fixed number of workers
; pm = static
; pm.max_children = 50

; Dynamic Mode: Auto-scaling workers
pm.max_children = 50            ; Maximum worker processes
pm.start_servers = 10           ; Initial workers on startup
pm.min_spare_servers = 5        ; Minimum idle workers
pm.max_spare_servers = 15       ; Maximum idle workers
pm.max_requests = 1000          ; Recycle worker after N requests (prevents memory leaks)

; OnDemand Mode: Spawn workers only when needed
; pm = ondemand
; pm.max_children = 50
; pm.process_idle_timeout = 10s

; Slow Request Logging
request_slowlog_timeout = 5s
slowlog = /var/log/php-fpm/slow.log

; Resource Limits
rlimit_files = 1024             ; Max open file descriptors per worker
rlimit_core = 0                 ; Core dump size limit

; Security
chroot = /var/www               ; Chroot jail (optional)
chdir = /                       ; Change directory after chroot

; Status Page (for monitoring)
pm.status_path = /status
ping.path = /ping
ping.response = pong

How to Calculate Optimal Worker Count

bash
# Formula: max_children = (Total RAM - OS RAM - Other Services) / PHP Process Memory

# 1. Check available RAM
free -m
# Example: 8GB server, 2GB for OS/other services = 6GB available

# 2. Measure average PHP worker memory
ps aux | grep php-fpm | awk '{sum+=$6} END {print sum/NR/1024 "MB"}'
# Example output: 50MB per worker

# 3. Calculate max_children
# 6GB (6144MB) / 50MB = 122 workers (theoretical max)
# Use 70-80% of theoretical max for safety
# Optimal: 122 * 0.75 = 91 workers

# 4. Set configuration
pm.max_children = 90
pm.start_servers = 20
pm.min_spare_servers = 10
pm.max_spare_servers = 30

Performance Tuning Tips:

  1. Unix Socket vs TCP: Unix socket is 10-15% faster for local connections
  2. Process Recycling: pm.max_requests prevents memory leaks
  3. Slow Log: Identify bottleneck requests
  4. Status Monitoring: Use /status endpoint with tools like Prometheus

3. Multi-Process Programming with pcntl

3.1 The pcntl Extension

The pcntl (Process Control) extension allows PHP to create and manage child processes.

Installation:

bash
# pcntl is usually compiled by default
php -m | grep pcntl

# If missing, recompile PHP with --enable-pcntl
./configure --enable-pcntl
make && make install

Basic Process Forking

php
<?php

/**
 * Basic Fork Example
 */
$pid = pcntl_fork();

if ($pid === -1) {
    // Fork failed
    die('Could not fork process');
    
} elseif ($pid === 0) {
    // Child process
    echo "[Child] My PID: " . getmypid() . "\n";
    echo "[Child] Parent PID: " . posix_getppid() . "\n";
    sleep(2);
    echo "[Child] Finished work\n";
    exit(0);
    
} else {
    // Parent process
    echo "[Parent] My PID: " . getmypid() . "\n";
    echo "[Parent] Child PID: $pid\n";
    
    // Wait for child to finish (important!)
    pcntl_wait($status);
    echo "[Parent] Child exited with status: $status\n";
}

Output:

[Parent] My PID: 12345
[Parent] Child PID: 12346
[Child] My PID: 12346
[Child] Parent PID: 12345
[Child] Finished work
[Parent] Child exited with status: 0

3.2 Practical Example: Parallel Task Processing

php
<?php

/**
 * Process Pool for Parallel Task Execution
 */
class ProcessPool {
    private int $maxWorkers;
    private array $workers = [];
    private array $tasks;
    
    public function __construct(int $maxWorkers = 4) {
        $this->maxWorkers = $maxWorkers;
    }
    
    public function addTask(callable $task): void {
        $this->tasks[] = $task;
    }
    
    public function run(): void {
        foreach ($this->tasks as $index => $task) {
            // Wait if we've reached max workers
            while (count($this->workers) >= $this->maxWorkers) {
                $this->reapChild();
            }
            
            $pid = pcntl_fork();
            
            if ($pid === -1) {
                throw new RuntimeException("Failed to fork process");
            } elseif ($pid === 0) {
                // Child process - execute task
                try {
                    $task($index);
                    exit(0);
                } catch (Exception $e) {
                    error_log("Task $index failed: " . $e->getMessage());
                    exit(1);
                }
            } else {
                // Parent process - track worker
                $this->workers[$pid] = $index;
            }
        }
        
        // Wait for all remaining workers
        while (count($this->workers) > 0) {
            $this->reapChild();
        }
    }
    
    private function reapChild(): void {
        $pid = pcntl_wait($status, WUNTRACED);
        
        if ($pid > 0) {
            $taskIndex = $this->workers[$pid];
            unset($this->workers[$pid]);
            
            $exitCode = pcntl_wexitstatus($status);
            echo "[ProcessPool] Task $taskIndex completed by PID $pid (exit: $exitCode)\n";
        }
    }
}

// Usage Example
$pool = new ProcessPool(maxWorkers: 4);

// Add CPU-intensive tasks
for ($i = 1; $i <= 10; $i++) {
    $pool->addTask(function($index) {
        echo "[Worker " . getmypid() . "] Processing task $index\n";
        
        // Simulate heavy computation
        $result = 0;
        for ($j = 0; $j < 1000000; $j++) {
            $result += sqrt($j);
        }
        
        sleep(rand(1, 3));
        echo "[Worker " . getmypid() . "] Task $index completed\n";
    });
}

$startTime = microtime(true);
$pool->run();
$endTime = microtime(true);

printf("Total execution time: %.2f seconds\n", $endTime - $startTime);

Performance Comparison:

  • Sequential execution (10 tasks): ~20 seconds
  • Parallel execution (4 workers): ~6 seconds
  • Speedup: 3.3x faster

3.3 Inter-Process Communication (IPC)

Method 1: Shared Memory

php
<?php

/**
 * Shared Memory for IPC
 */
function useSharedMemory(): void {
    $shmKey = ftok(__FILE__, 't');
    $shmId = shmop_open($shmKey, "c", 0644, 1024);
    
    $pid = pcntl_fork();
    
    if ($pid === 0) {
        // Child process - write to shared memory
        sleep(1);
        $data = "Hello from child!";
        shmop_write($shmId, $data, 0);
        exit;
    } else {
        // Parent process - read from shared memory
        pcntl_wait($status);
        $data = shmop_read($shmId, 0, 1024);
        echo "Parent received: " . trim($data) . "\n";
        shmop_delete($shmId);
    }
}

Method 2: Message Queues

php
<?php

/**
 * System V Message Queues
 */
class MessageQueue {
    private $queue;
    private int $key;
    
    public function __construct() {
        $this->key = ftok(__FILE__, 'm');
        $this->queue = msg_get_queue($this->key);
    }
    
    public function send(string $message, int $type = 1): bool {
        return msg_send($this->queue, $type, $message, false, false);
    }
    
    public function receive(int $type = 1): ?string {
        $msgType = null;
        $message = null;
        
        if (msg_receive($this->queue, $type, $msgType, 1024, $message, false, MSG_IPC_NOWAIT)) {
            return $message;
        }
        
        return null;
    }
    
    public function __destruct() {
        msg_remove_queue($this->queue);
    }
}

// Usage
$mq = new MessageQueue();

$pid = pcntl_fork();

if ($pid === 0) {
    // Child - producer
    for ($i = 1; $i <= 5; $i++) {
        $mq->send("Message $i from child");
        sleep(1);
    }
    exit;
} else {
    // Parent - consumer
    while (true) {
        $message = $mq->receive();
        if ($message !== null) {
            echo "Received: $message\n";
        } else {
            usleep(100000); // 100ms
        }
        
        // Check if child finished
        $res = pcntl_waitpid($pid, $status, WNOHANG);
        if ($res == $pid) {
            break;
        }
    }
}

Method 3: Unix Pipes

php
<?php

/**
 * Pipe Communication
 */
function usePipes(): void {
    $descriptors = [
        0 => ["pipe", "r"],  // stdin
        1 => ["pipe", "w"],  // stdout
        2 => ["pipe", "w"]   // stderr
    ];
    
    $process = proc_open("cat", $descriptors, $pipes);
    
    if (is_resource($process)) {
        // Write to process stdin
        fwrite($pipes[0], "Hello from PHP!\n");
        fclose($pipes[0]);
        
        // Read from process stdout
        echo "Process output: " . stream_get_contents($pipes[1]);
        fclose($pipes[1]);
        
        // Clean up
        proc_close($process);
    }
}

4. PHP Thread Safety

4.1 ZTS (Zend Thread Safety)

PHP comes in two flavors:

  • NTS (Non-Thread-Safe): For single-threaded environments (PHP-FPM, CLI)
  • ZTS (Zend Thread-Safe): For multi-threaded environments (Apache with mod_php)
bash
# Check your PHP build
php -v
# Output example:
# PHP 8.3.0 (cli) (built: Nov  1 2024 10:15:23) (NTS)
#                                                   ^^^
#                                            NTS = Non-Thread-Safe

# Or use php -i
php -i | grep "Thread Safety"
# Thread Safety => disabled  (NTS)
# Thread Safety => enabled   (ZTS)

Why Thread Safety Matters

php
// This is why NTS exists - global state in PHP
$globalVar = "shared data";

function unsafeFunction() {
    global $globalVar;
    $globalVar .= " modified";
    // In multi-threaded environment, this causes race conditions!
}

ZTS Protection Mechanism (Simplified):

c
// TSRM (Thread Safe Resource Manager)
// Each thread gets its own copy of global variables

#ifdef ZTS
# define TSRMLS_D    void ***tsrm_ls
# define TSRMLS_C    tsrm_ls
# define GLOBAL(v)   (*((v##_type *) (*((void ***) tsrm_ls))[v##_id]))
#else
# define TSRMLS_D
# define TSRMLS_C
# define GLOBAL(v)   v
#endif

4.2 pthreads Extension

⚠️ Warning: pthreads is only available for PHP 7.2 and requires ZTS build. For modern PHP (8.x), use parallel extension or multi-process approaches.

php
<?php

/**
 * pthreads Example (PHP 7.2 with ZTS)
 */
class WorkerThread extends Thread {
    private string $data;
    
    public function __construct(string $data) {
        $this->data = $data;
    }
    
    public function run(): void {
        echo "[Thread " . $this->getThreadId() . "] Processing: {$this->data}\n";
        sleep(2);
        echo "[Thread " . $this->getThreadId() . "] Completed\n";
    }
}

// Create threads
$threads = [];
for ($i = 0; $i < 4; $i++) {
    $thread = new WorkerThread("Task $i");
    $thread->start();
    $threads[] = $thread;
}

// Wait for all threads
foreach ($threads as $thread) {
    $thread->join();
}

echo "All threads completed!\n";

4.3 Modern Alternative: parallel Extension

For PHP 8.x, use the parallel extension (requires ZTS):

php
<?php

use parallel\Runtime;
use parallel\Channel;

/**
 * Modern parallel execution with PHP 8
 */
$runtime = new Runtime();
$channel = new Channel();

// Submit task
$future = $runtime->run(function() use ($channel) {
    $result = expensive_calculation();
    $channel->send($result);
});

// Do other work while task runs
perform_other_tasks();

// Get result
$result = $channel->recv();

5. Concurrency Optimization Strategies

5.1 Process Pool Management

php
<?php

/**
 * Production-Grade Process Pool
 */
class ProductionProcessPool {
    private int $minWorkers;
    private int $maxWorkers;
    private int $maxIdleTime = 300; // 5 minutes
    private array $workers = [];
    private array $queue = [];
    
    public function __construct(int $minWorkers = 5, int $maxWorkers = 20) {
        $this->minWorkers = $minWorkers;
        $this->maxWorkers = $maxWorkers;
        
        // Initialize minimum workers
        for ($i = 0; $i < $minWorkers; $i++) {
            $this->spawnWorker();
        }
    }
    
    private function spawnWorker(): void {
        $pid = pcntl_fork();
        
        if ($pid === 0) {
            // Child process
            $this->workerLoop();
            exit(0);
        } else {
            $this->workers[$pid] = [
                'spawned' => time(),
                'last_active' => time(),
                'tasks_completed' => 0
            ];
        }
    }
    
    private function workerLoop(): void {
        while (true) {
            // Check for tasks in shared memory/queue
            $task = $this->fetchTask();
            
            if ($task !== null) {
                $this->executeTask($task);
            } else {
                // Idle - check if should terminate
                if ($this->shouldTerminate()) {
                    break;
                }
                usleep(100000); // 100ms
            }
        }
    }
    
    private function shouldTerminate(): bool {
        // Only terminate if:
        // 1. We have more than minimum workers
        // 2. Worker has been idle for too long
        return count($this->workers) > $this->minWorkers
            && (time() - $this->lastActive) > $this->maxIdleTime;
    }
    
    public function submitTask(callable $task): void {
        // Add to queue
        $this->queue[] = $task;
        
        // Scale up if needed
        if ($this->shouldScaleUp()) {
            $this->spawnWorker();
        }
    }
    
    private function shouldScaleUp(): bool {
        $queueSize = count($this->queue);
        $workerCount = count($this->workers);
        
        return $queueSize > $workerCount 
            && $workerCount < $this->maxWorkers;
    }
}

5.2 Resource Isolation

php
<?php

/**
 * Prevent resource leaks between requests
 */
class ResourceManager {
    private array $connections = [];
    
    public function getConnection(string $dsn): PDO {
        $key = md5($dsn);
        
        if (!isset($this->connections[$key])) {
            $this->connections[$key] = new PDO($dsn);
        }
        
        return $this->connections[$key];
    }
    
    public function cleanup(): void {
        foreach ($this->connections as $conn) {
            $conn = null; // Close connection
        }
        $this->connections = [];
        
        // Force garbage collection
        gc_collect_cycles();
    }
}

// In PHP-FPM, cleanup after each request
register_shutdown_function(function() {
    global $resourceManager;
    $resourceManager->cleanup();
});

5.3 Performance Tuning Best Practices

1. OPcache Configuration

ini
; /etc/php/8.3/cli/conf.d/10-opcache.ini

[opcache]
opcache.enable=1
opcache.enable_cli=1              ; Enable for CLI scripts too
opcache.memory_consumption=256    ; 256MB OPcache memory
opcache.interned_strings_buffer=16 ; String interning
opcache.max_accelerated_files=10000
opcache.revalidate_freq=2         ; Check file changes every 2 seconds (production: 60)
opcache.fast_shutdown=1
opcache.validate_timestamps=1     ; Production: 0 (disable for performance)

2. Preloading (PHP 7.4+)

php
// /path/to/preload.php

<?php

/**
 * Preload frequently used classes into memory
 */
opcache_compile_file(__DIR__ . '/vendor/autoload.php');
opcache_compile_file(__DIR__ . '/app/Models/User.php');
opcache_compile_file(__DIR__ . '/app/Controllers/HomeController.php');
// ... more files
ini
; php.ini
opcache.preload=/path/to/preload.php
opcache.preload_user=www-data

Performance Gain: 10-30% faster request handling

3. JIT (Just-In-Time Compilation) - PHP 8+

ini
; php.ini

opcache.enable=1
opcache.jit_buffer_size=100M
opcache.jit=tracing               ; or "function" for function-level JIT

; JIT optimization levels:
; 0    = Disabled
; 1205 = tracing + profile-guided optimization (recommended)
; 1255 = maximum optimization

JIT Performance:

  • CPU-bound tasks: 2-3x faster
  • I/O-bound tasks (typical web apps): 5-15% improvement
  • Best for: Mathematical computations, image processing, encoding

6. Production Best Practices

6.1 Monitoring PHP-FPM

Status Endpoint Configuration

ini
; /etc/php/8.3/fpm/pool.d/www.conf

pm.status_path = /php-fpm-status
ping.path = /ping
ping.response = pong

Nginx Configuration

nginx
server {
    listen 80;
    server_name example.com;
    
    # PHP-FPM Status (restrict to localhost)
    location /php-fpm-status {
        access_log off;
        allow 127.0.0.1;
        deny all;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
    
    # Ping endpoint
    location /ping {
        access_log off;
        allow 127.0.0.1;
        deny all;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

Monitor with curl

bash
# Full status
curl http://localhost/php-fpm-status?full

# Output:
# pool:                 www
# process manager:      dynamic
# start time:           12/Nov/2025:10:00:00 +0000
# start since:          3600
# accepted conn:        1250
# listen queue:         0
# max listen queue:     1
# listen queue len:     128
# idle processes:       8
# active processes:     2
# total processes:      10
# max active processes: 5
# max children reached: 0
# slow requests:        3

# JSON output
curl http://localhost/php-fpm-status?json

Prometheus Exporter

php
<?php
/**
 * php-fpm-exporter.php
 * Export PHP-FPM metrics to Prometheus
 */

$status = file_get_contents('http://localhost/php-fpm-status?json');
$data = json_decode($status, true);

echo "# HELP phpfpm_accepted_conn Total accepted connections\n";
echo "# TYPE phpfpm_accepted_conn counter\n";
echo "phpfpm_accepted_conn " . $data['accepted conn'] . "\n";

echo "# HELP phpfpm_active_processes Current active processes\n";
echo "# TYPE phpfpm_active_processes gauge\n";
echo "phpfpm_active_processes " . $data['active processes'] . "\n";

echo "# HELP phpfpm_idle_processes Current idle processes\n";
echo "# TYPE phpfpm_idle_processes gauge\n";
echo "phpfpm_idle_processes " . $data['idle processes'] . "\n";

echo "# HELP phpfpm_slow_requests Total slow requests\n";
echo "# TYPE phpfpm_slow_requests counter\n";
echo "phpfpm_slow_requests " . $data['slow requests'] . "\n";

6.2 Common Pitfalls and Solutions

Problem 1: Memory Leaks

php
<?php

// ❌ BAD: Circular references cause memory leaks
class Parent {
    public $child;
}

class Child {
    public $parent;
}

$parent = new Parent();
$child = new Child();
$parent->child = $child;
$child->parent = $parent;
// Memory won't be freed even after $parent and $child go out of scope

// ✅ GOOD: Break circular references
class Parent {
    public $child;
    
    public function __destruct() {
        $this->child = null; // Break reference
    }
}

Solution: Use pm.max_requests to recycle workers:

ini
pm.max_requests = 1000  ; Restart worker after 1000 requests

Problem 2: Process Pool Exhaustion

bash
# Symptoms
tail -f /var/log/php-fpm/error.log
# [pool www] server reached max_children setting (50), consider raising it

Solution:

ini
# Calculate optimal value
# max_children = (RAM - OS) / avg_process_memory

pm.max_children = 100
pm.start_servers = 25
pm.min_spare_servers = 10
pm.max_spare_servers = 35

Problem 3: Slow Requests

bash
# Enable slow log
tail -f /var/log/php-fpm/slow.log

Solution: Analyze slow queries and optimize

php
// Use profiling
xdebug_start_trace();
slow_function();
xdebug_stop_trace();

// Or use APM tools like New Relic, Datadog

7. Comparison with Other Languages

7.1 PHP vs Golang

FeaturePHPGolang
Concurrency ModelMulti-process (PHP-FPM)Goroutines (lightweight threads)
Memory per Worker50-100MB2-5KB per goroutine
Context SwitchingHeavy (OS-level)Light (user-space)
Shared MemoryComplex (SHM, message queues)Built-in channels
PerformanceGood (with OPcache/JIT)Excellent (native compilation)
Use CaseWeb applicationsMicroservices, concurrent tasks

Golang Goroutine Example (for comparison):

go
// Golang: 10,000 concurrent tasks trivially
for i := 0; i < 10000; i++ {
    go func(id int) {
        processTask(id)
    }(i)
}

PHP Equivalent (limited by process count):

php
// PHP: Limited to ~100-200 concurrent processes
$pool = new ProcessPool(maxWorkers: 100);
for ($i = 0; $i < 10000; $i++) {
    $pool->addTask(fn() => processTask($i));
}

See also: Advanced Go Concurrency Patterns


7.2 PHP vs Node.js

FeaturePHPNode.js
Concurrency ModelMulti-processEvent loop + libuv
Memory UsageHigher (50-100MB/worker)Lower (10-20MB)
CPU-bound TasksBetter (separate processes)Blocks event loop
I/O-bound TasksSimilar performanceExcellent (async I/O)
Shared StateNone (process isolation)Global (careful with race conditions)
Learning CurveEasy (synchronous)Moderate (async/await, promises)

Node.js Event Loop (for comparison):

javascript
// Node.js: Non-blocking I/O
const fs = require('fs').promises;

async function readFiles() {
    const files = await Promise.all([
        fs.readFile('file1.txt'),
        fs.readFile('file2.txt'),
        fs.readFile('file3.txt')
    ]);
    // All files read concurrently
}

PHP Equivalent:

php
// PHP: Multi-process for concurrency
$pool = new ProcessPool(3);
$pool->addTask(fn() => file_get_contents('file1.txt'));
$pool->addTask(fn() => file_get_contents('file2.txt'));
$pool->addTask(fn() => file_get_contents('file3.txt'));
$pool->run();

7.3 PHP vs Python

FeaturePHPPython
Concurrency ModelMulti-process (PHP-FPM)Multi-process/thread (GIL limitation)
True ParallelismYes (separate processes)No (GIL prevents true multithreading)
Process OverheadSimilarSimilar
Async SupportLimited (ReactPHP, Swoole)Excellent (asyncio)
Use CaseWeb applicationsData science, scripting, web

Python GIL Issue:

python
# Python: GIL prevents true parallelism in threads
import threading

def cpu_task():
    sum(range(10000000))

# These threads run sequentially due to GIL
threads = [threading.Thread(target=cpu_task) for _ in range(4)]
for t in threads:
    t.start()

PHP Advantage: No GIL, true parallelism with processes.


8. Advanced: Async PHP with Swoole

For high-concurrency scenarios, consider Swoole extension:

php
<?php

use Swoole\HTTP\Server;
use Swoole\HTTP\Request;
use Swoole\HTTP\Response;

/**
 * Swoole: Async/coroutine support for PHP
 */
$server = new Server("0.0.0.0", 9501);

$server->set([
    'worker_num' => 4,      // Worker processes
    'max_request' => 10000,
    'enable_coroutine' => true,
]);

$server->on('request', function (Request $request, Response $response) {
    // Coroutine: non-blocking concurrent requests
    go(function () use ($response) {
        $client = new Swoole\Coroutine\Http\Client('api.example.com', 443, true);
        $client->get('/data');
        $result = $client->body;
        
        $response->end($result);
    });
});

$server->start();

Swoole Performance:

  • 10,000+ concurrent connections on single server
  • 100,000+ QPS (Queries Per Second)
  • Native coroutine support (like Go goroutines)

9. Conclusion

Key Takeaways

  1. PHP-FPM is production-ready: Properly configured, it handles millions of requests efficiently
  2. Process vs Thread: PHP's multi-process model provides excellent isolation but higher memory usage
  3. pcntl for CLI: Use multi-process for CPU-intensive batch jobs
  4. Monitoring is crucial: Use status endpoints, slow logs, and APM tools
  5. Modern PHP is fast: OPcache, JIT, and preloading significantly boost performance

When to Use What

ScenarioSolution
Web applicationsPHP-FPM + Nginx/Apache
CLI batch processingpcntl multi-process
High-concurrency APISwoole/RoadRunner
CPU-bound tasksSeparate processes or switch to Go
I/O-bound tasksAsync PHP (Swoole) or Node.js

10. References


Last Updated: November 12, 2025
Author: PFinal南丞
License: CC BY-SA 4.0

Last updated: