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 thevalue()
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.
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.