Explore how to implement multithreading in PHP, from using the parallel extension to alternative methods for concurrent programming
Ide

Getting started with multithreading in PHP

Muhammad Iqbal Hanif

I’m sure as employees (developers or not), we juggle multiple things at once. If you’re a developer, you’ve probably juggled meetings, multiple JIRA tickets, writing test scripts, handling API requests, writing documentation……I know the list does NOT end!

Although I’m not here to relieve you of juggling daily duties (I feel you), I have something else to help you with server-side duties in PHP. Like you creating clones of yourselves that handle one task at once.

Introducing multithreading in PHP (yes, it is possible!) to handle multiple tasks together. I know multithreading is way more complex than it sounds, but that’s what I’m here for to make the concept easy enough so it is easy to implement.

Without further ado, let’s get started.

What is multithreading?

Multithreading is a programming technique for handling several processes or tasks together. As the name suggests, it simply means that a program can run multiple threads concurrently by using multiple CPU cores.

Multithreading in PHP

PHP, primarily known for its web development capabilities, was not originally designed with multithreading in mind. Early on, developers seeking parallelism in PHP were limited to methods like process forking through pcntl_fork. While effective, this approach had challenges, including difficulties in managing child processes and handling results cleanly.

Then came pthreads, an object-oriented API that brought true multithreading to PHP. With pthreads, developers could create, read, write, and synchronize with threads, workers, and threaded objects. This was a significant leap forward, allowing PHP applications to use multicore processors and run tasks in parallel.

Discontinuation of pthreads extension

However, pthreads was discontinued in 2019 after PHP 7.4 due to architectural flaws, and parallel was introduced.

Parallel is a PHP interpreter thread that comes with an autoloader. provides a straightforward API with core components like:

Component Description
Runtime a PHP interpreter thread
Future to access task results
Channel for communication between tasks
Tasks a 'closure' for parallel execution
Events enabling event-driven programming

Developers can efficiently manage concurrent tasks through these tools, optimizing performance for resource-intensive operations.

Step 1: Installing the Parallel Extension

For modern PHP applications, the recommended approach is to use the parallel extension. This extension provides a more robust and safer way to implement multithreading in PHP.

  • Before writing multithreaded PHP code, you must install and enable the parallel extension in your PHP environment. Ensure your PHP version is 7.2 or higher, as parallel is only compatible with PHP 7.2+.
  • If you’re using Linux or macOS, you can install parallel via PECL.
  • For Windows, you may need to download the appropriate DLL from the official PHP extension repository and configure your php.ini file to include it:
extension=php_parallel.dll
  • To verify that the parallel extension is successfully installed, you can run:
if (extension_loaded('parallel')) {
    echo "The parallel extension is loaded.\n";
} else {
    echo "The parallel extension is not loaded.\n";
}

If the extension is loaded, you can start multithreading with parallel.

Step 2: Basic setup and usage

In parallel, you don’t deal directly with thread classes like in pthreads. Instead, you work with:

  • Runtime is used to create a new environment for executing tasks.
  • Future represents the result of the task, which is retrieved using the value() method.

Here’s a basic example:

use parallel\{Runtime, Future};

// Create a new runtime environment
$runtime = new Runtime();

try {
    // Run a task in parallel and return a result
    $future = $runtime->run(function() {
        return "Hello from parallel thread!";
    });

    // Fetch and display the result from the parallel task
    echo $future->value();
} catch (parallel\Runtime\Error $e) {
    echo "Runtime error: " . $e->getMessage();
} catch (parallel\Future\Error $e) {
    echo "Future error: " . $e->getMessage();
}

While parallel abstracts away much of the complexity, it’s still crucial to manage resources and synchronization carefully.

Unlike pthreads, parallel handles most synchronization internally, but you should still avoid shared state wherever possible. Instead, pass data into tasks and return results using Future objects.

Using channels for communication

If you need to communicate between threads, parallel provides Channels:

use parallel\{Runtime, Channel};

$channel = Channel::make("my_channel", Channel::Infinite);

$runtime->run(function($channel) {
    $channel->send("Data from thread!");
}, [$channel]);

echo $channel->recv();

Channels offer a thread-safe way to send and receive messages between threads.

Other features of Parallel for multithreading in PHP

Events

The parallel\Events API allows you to create an event loop to handle multiple asynchronous operations, such as channels or futures. It’s useful for coordinating multiple tasks that need to run concurrently.

use parallel\{Runtime, Events, Future, Channel};

$runtime = new Runtime();
$events = new Events();

$channel = Channel::make("example_channel");

// Add future to event loop
$future = $runtime->run(function($channel) {
    $channel->send("Message from parallel task");
    return "Task completed";
}, [$channel]);

$events->addFuture($future);

// Add channel to event loop
$events->addChannel($channel);

foreach ($events as $event) {
    if ($event->source() === $future) {
        echo $event->value(); // Output: Task completed
    } elseif ($event->source() === $channel) {
        echo $event->value(); // Output: Message from parallel task
    }
}

Synchronization

The parallel\Sync class synchronizes threads, ensuring safe access to shared resources. It’s useful when multiple threads need to read or write to the same data.

use parallel\{Runtime, Sync};

$runtime = new Runtime();
$sync = new Sync(0); // Initialize Sync object with a value

$future = $runtime->run(function($sync) {
    $sync->lock();
    $value = $sync->get();
    $sync->set($value + 1); // Safely modify shared resource
    $sync->unlock();
    return $sync->get();
}, [$sync]);

echo "Final value: " . $future->value(); // Output: Final value: 1

Events API

The parallel\Events API allows the creation of an event-driven model that can handle multiple tasks concurrently. It manages event loops, handling communication between channels and future tasks.

use parallel\{Events, Future, Runtime};

$runtime = new Runtime();
$events = new Events();

$future1 = $runtime->run(function() {
    sleep(1);
    return "Task 1 completed";
});

$future2 = $runtime->run(function() {
    sleep(2);
    return "Task 2 completed";
});

$events->addFuture($future1);
$events->addFuture($future2);

foreach ($events as $event) {
    echo $event->value() . "\n";
}

Troubleshooting common multithreading issues in Parallel

Bugs in multithreading can be quite challenging, even making you want to remove multithreading entirely from your program.

Multithreading meme

Source: Reddit

Although I can’t predict the kinds of nasty bugs you might have to deal with, I do have some common issues you might face and how to avoid them.

Race conditions

A race condition occurs when multiple threads try to modify shared data simultaneously, leading to unpredictable results. To avoid this, ensure to use Channels or Sync objects to manage access to shared resources.

use parallel\{Runtime, Sync};

$sync = new Sync(0);
$runtime = new Runtime();

$future = $runtime->run(function($sync) {
    $sync->lock();
    $value = $sync->get();
    $sync->set($value + 1);
    $sync->unlock();
    return $sync->get();
}, [$sync]);

echo $future->value();  // Output: 1

Deadlocks

Deadlocks happen when two or more threads are waiting indefinitely for resources held by each other.

To avoid this issue, ensure proper resource locking and unlocking and avoid circular dependencies.

use parallel\{Runtime, Sync};

$sync1 = new Sync(0);
$sync2 = new Sync(0);
$runtime = new Runtime();

$future = $runtime->run(function($sync1, $sync2) {
    $sync1->lock();
    $sync2->lock();
    // Perform tasks
    $sync2->unlock();
    $sync1->unlock();
    return "Done";
}, [$sync1, $sync2]);

echo $future->value();

Memory management

PHP’s garbage collector is designed for single-threaded environments, so you must be cautious when working with threads. Avoid long-running threads that consume large amounts of memory, and ensure that resources are properly released when threads finish their tasks.

use parallel\Runtime;

$runtime = new Runtime();

$future = $runtime->run(function() {

$data = range(1, 1000000); // Large data set
// Processing...
unset($data); // Free memory
return "Task completed";
});

echo $future->value();

Compatibility issues

Not all PHP extensions are thread-safe. Before using parallel in a production environment, verify that all other extensions and libraries your application depends on are compatible with multithreading.

Using multithreading in PHP using online compilers

Implementing true multithreading in PHP via the parallel extension in online compilers like JDoodle can be challenging due to the need for system-level support.

However, JDoodle allows you to import external libraries from Packagist, enabling alternative methods for concurrent programming.

While using parallel might be tough, you can still achieve concurrency with other approaches, such as asynchronous HTTP requests. Just ensure the online environment has internet access enabled to make the most out of these techniques.

Dive Deeper into More Topics

Get ready to go beyond the surface and explore a treasure trove of diverse topics!