Concurrency vs parallelism — doing multiple things at once vs truly simultaneously
Intermediate5 min read·eng-20-010
interviewperformancecompare
Concept
Concurrency vs parallelism — two related but distinct concepts about executing multiple tasks.
Concurrency: Multiple tasks are IN PROGRESS at the same time, but not necessarily executing simultaneously. Tasks take turns using the CPU. One task runs, pauses (e.g., waiting for I/O), another task runs. Concurrency is about DEALING WITH multiple things at once.
Parallelism: Multiple tasks are LITERALLY executing at the same time on different CPU cores. Parallelism requires multiple processors or cores. It's about DOING multiple things at once.
The key distinction: Concurrency is possible even on a single core (via task switching). Parallelism requires multiple cores.
Analogy:
- Concurrency: One chef cooking multiple dishes — working on dish A, then while it simmers, working on dish B. One chef, multiple dishes in progress.
- Parallelism: Two chefs each working on a different dish simultaneously. Two chefs, two dishes literally at the same time.
PHP and concurrency/parallelism:
- Traditional PHP is synchronous — one thing at a time per process.
- PHP-FPM achieves concurrency through multiple processes — 50 FPM workers handle 50 requests concurrently (but each worker is single-threaded).
- Fibers (PHP 8.1): Cooperative concurrency within a single process.
- ReactPHP / Amp: Event loop for async I/O — concurrency without threads.
- Parallel extension / pcntl_fork: True parallelism in PHP (rare).
- Queue workers: Multiple queue workers run in parallel on different processes.
I/O-bound vs CPU-bound:
- I/O-bound: Waiting for database, API, disk. Concurrency helps (while waiting for DB, handle another request).
- CPU-bound: Heavy computation. True parallelism needed (multiple cores).
Code Example
php
<?php
// PHP CONCURRENCY — multiple FPM workers (not threads)
// Config: /etc/php/8.4/fpm/pool.d/www.conf
// pm.max_children = 50 ← 50 concurrent requests handled simultaneously
// Each worker is independent process — no shared memory, no threading issues
// COOPERATIVE CONCURRENCY with Fibers (PHP 8.1)
// Single process, but can "yield" control during I/O waits
$fiber = new Fiber(function (): void {
$value = Fiber::suspend('waiting for data');
echo "Got: {$value}\n";
});
$fiber->start(); // starts, hits suspend
$fiber->resume('hello'); // resumes with value → prints "Got: hello"
// PRACTICAL ASYNC with GUZZLE (HTTP concurrency)
use GuzzleHttp\Client;
use GuzzleHttp\Promise\Utils;
$client = new Client();
// SEQUENTIAL (slow — waits for each request)
$api1 = $client->get('https://api.example.com/users')->getBody(); // 200ms
$api2 = $client->get('https://api.example.com/orders')->getBody(); // 200ms
// Total: 400ms
// CONCURRENT (fast — both requests in flight simultaneously)
$promises = [
'users' => $client->getAsync('https://api.example.com/users'),
'orders' => $client->getAsync('https://api.example.com/orders'),
];
$results = Utils::unwrap($promises); // fires both, waits for both
// Total: ~200ms (max of the two, not sum)
// PARALLELISM with queue workers
// dispatch() jobs, run multiple workers:
// php artisan queue:work &
// php artisan queue:work &
// php artisan queue:work &
// These 3 workers TRULY run in parallel on different CPU cores
dispatch(new ProcessPaymentJob($order1)); // → worker 1
dispatch(new ProcessPaymentJob($order2)); // → worker 2
dispatch(new ProcessPaymentJob($order3)); // → worker 3
// All 3 jobs run at the same time on 3 different worker processes