0

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