Home > Backend Development > PHP Tutorial > Parallel Programming with Pthreads in PHP - the Fundamentals

Parallel Programming with Pthreads in PHP - the Fundamentals

Jennifer Aniston
Release: 2025-02-10 08:57:09
Original
647 people have browsed it

Parallel Programming with Pthreads in PHP - the Fundamentals

Key Points

  • Avoid using pthreads in web server environments: Due to security and scalability issues, pthreads should not be used in web server environments such as FCGI, because it cannot effectively handle multiple environments in these environments Thread.
  • Use pthreads for one-time tasks or IO binding operations: For tasks that perform one or require a large number of IO operations, using pthreads can help uninstall the main execution thread and handle it in a separate thread These operations are used to improve performance.
  • Recycle threads to optimize resources: Creating a new thread for each task can take up a lot of resources; instead, reuse threads through the Worker or Pool class to more efficiently manage and execute multiple tasks.
  • Understand the invariance of pthreads and the Volatile class: By default, the properties of objects that extend Threaded are immutable to avoid performance degradation, and the Volatile class provides a way to manage mutables when necessary. Method of attributes.
  • Implement synchronization for thread safety: To prevent data corruption and ensure consistent results when multiple threads access shared resources, use the synchronization methods provided by pthreads, such as synchronization blocks and Threaded::wait and Threaded::notify and other methods.

This article was reviewed by Christopher Pitt. Thanks to all SitePoint peer reviewers for making SitePoint content perfect!


PHP developers seem to seldom take advantage of parallelism. The simplicity of synchronous, single-threaded programming is really attractive, but sometimes using a little concurrency can lead to some worthwhile performance improvements.

In this article, we will learn how to implement threads in PHP using the pthreads extension. This requires the installation of ZTS (Zend thread-safe) version of PHP 7.x, as well as the installation of pthreads v3. (At the time of writing, PHP 7.1 users need to install from the master branch of the pthreads repo - see some of this article for more information on building third-party extensions from source.)

A quick explanation: pthreads v2 is aimed at PHP 5.x, no longer supported; pthreads v3 is aimed at PHP 7.x, and is under active development.

Parallel Programming with Pthreads in PHP - the Fundamentals

Thank you so much to Joe Watkins (creator of the pthreads extension) for proofreading and helping improve my post!

When not to use pthreads

Before we proceed, I want to first explain the situation where you should not (and cannot ) use the pthreads extension.

In pthreads v2, it is not recommended to use pthreads in a web server environment (i.e., in an FCGI process). Starting with pthreads v3, this suggestion is enforced, so you can't use it in a web server environment at all. Two main reasons for doing this are:

  1. It is not safe to use multiple threads in this environment (which can cause IO issues and other issues).
  2. It doesn't scale well. For example, suppose you have a PHP script that creates a new thread to handle some work, and that script executes every time you request it. This means that for each request, your application creates a new thread (this is a 1:1 thread model – one thread corresponds to one request). If your application processes 1,000 requests per second, it creates 1,000 threads per second! Running so many threads on a single machine will quickly overwhelm it, and this problem will only exacerbate as the request rate increases.

This is why threads are not a good solution in this environment. If you are looking for a solution for threads as IO blocking tasks (such as performing HTTP requests), then let me point you the direction of asynchronous programming , which can be achieved through frameworks such as Amp. SitePoint has published some excellent articles on this topic (such as writing asynchronous libraries and modifying Minecraft with PHP), if you are interested.

Get back to the point, let's get straight to the topic!

Training one-time tasks

Sometimes you want to handle one-time tasks in a multi-threaded way (such as performing some IO-bound tasks). In this case, you can use the Thread class to create a new thread and run some units of work in that separate thread.

Example:

$task = new class extends Thread {
    private $response;

    public function run()
    {
        $content = file_get_contents("http://google.com");
        preg_match("~<title>(.+)</title>~", $content, $matches);
        $this->response = $matches[1];
    }
};

$task->start() && $task->join();

var_dump($task->response); // string(6) "Google"
Copy after login
Copy after login
Copy after login

Above, the run method is the unit of work we will execute in a new thread. When Thread::start is called, a new thread is generated and the run method is called. We then rejoin the generated thread to the main thread (via Thread::join), which will block until the individual thread completes execution. This ensures that the task has completed execution before attempting to output the result (stored in $task->response).

The responsibilities of contaminating the class with thread-related logic (including having to define run methods) may not be ideal. We can isolate these classes by having them extend the Threaded class, and then run them in other threads:

class Task extends Threaded
{
    public $response;

    public function someWork()
    {
        $content = file_get_contents('http://google.com');
        preg_match('~<title>(.+)</title>~', $content, $matches);
        $this->response = $matches[1];
    }
}

$task = new Task;

$thread = new class($task) extends Thread {
    private $task;

    public function __construct(Threaded $task)
    {
        $this->task = $task;
    }

    public function run()
    {
        $this->task->someWork();
    }
};

$thread->start() && $thread->join();

var_dump($task->response);
Copy after login
Copy after login
Copy after login

Any class that needs to be run in a separate thread will must extend the Threaded class in some way. This is because it provides the necessary capabilities to run in different threads, as well as providing implicit security and useful interfaces (for resource synchronization, etc.).

Let's quickly understand the class hierarchy exposed by pthreads:

<code>Threaded (implements Traversable, Collectable)
    Thread
        Worker
    Volatile
Pool</code>
Copy after login
Copy after login
We have already learned the basics of the Thread and Threaded classes, so let's look at the remaining three (Worker, Volatile, and Pool).

Recycle thread

It is expensive to start a new thread for each task to be parallelized. This is because in order to implement threads inside PHP, pthreads must adopt a shared stateless architecture. This means that the entire execution context (including each class, interface, attribute, and function) of the current instance of the PHP interpreter must be copied for each thread created. Since this can have a significant performance impact, threads should always be reused as much as possible. There are two ways to reuse threads: using Worker or using Pool.

The Worker class is used to perform a series of tasks synchronously in another thread. This is done by creating a new Worker instance (this will create a new thread) and then stacking tasks onto that separate thread (via Worker::stack).

This is a simple example:

$task = new class extends Thread {
    private $response;

    public function run()
    {
        $content = file_get_contents("http://google.com");
        preg_match("~<title>(.+)</title>~", $content, $matches);
        $this->response = $matches[1];
    }
};

$task->start() && $task->join();

var_dump($task->response); // string(6) "Google"
Copy after login
Copy after login
Copy after login

Output:

Parallel Programming with Pthreads in PHP - the Fundamentals

The above stacks 15 tasks onto the new $worker object via Worker::stack and then process them in stacking order. As shown above, the Worker::collect method is used to clean up tasks after the task has completed execution. By using it in the while loop, we block the main thread until all stacked tasks have been executed and have been cleaned up, and then we trigger Worker::shutdown. Closing the worker prematurely (i.e., while tasks still have to be executed) will still block the main thread until all tasks are finished executing - tasks just won't be garbage collected (causing memory leaks).

The

The Worker class provides some other task stack-related methods, including Worker::unstack for deleting the oldest stack items, and Worker::getStacked for executing the number of items on the stack. The stack of a worker only saves the tasks to be executed. Once the task in the stack is executed, it is deleted and placed on another (internal) stack for garbage collection (using Worker::collect).

Another way to reuse threads when performing many tasks is to use thread pools (via the Pool class). A thread pool is driven by a set of Workers to enable tasks to be executed concurrently, where the concurrency factor (the number of threads the pool runs) is specified at the time of pool creation. Let's adjust the example above to use the worker pool:

Output:
class Task extends Threaded
{
    public $response;

    public function someWork()
    {
        $content = file_get_contents('http://google.com');
        preg_match('~<title>(.+)</title>~', $content, $matches);
        $this->response = $matches[1];
    }
}

$task = new Task;

$thread = new class($task) extends Thread {
    private $task;

    public function __construct(Threaded $task)
    {
        $this->task = $task;
    }

    public function run()
    {
        $this->task->someWork();
    }
};

$thread->start() && $thread->join();

var_dump($task->response);
Copy after login
Copy after login
Copy after login

Parallel Programming with Pthreads in PHP - the Fundamentals There are some significant differences between using pools and using worker programs. First, pools do not need to be started manually, they start executing tasks as soon as they are available. Secondly, we submit the tasks

to the pool instead of stacking them. Additionally, the Pool class does not extend Threaded, so it may not be passed to other threads (unlike Worker).

As a good practice, you should always collect tasks for worker programs and pools after completing tasks and close them manually. Threads created through the Thread class should also be rejoined to the creator thread.

pthreads and (non)variability

The last class to be introduced is Volatile—a new addition to pthreads v3. Invariance has become an important concept in pthreads because without it, performance will be severely degraded. Therefore, by default, properties of the Threaded class that is itself a Threaded object are now immutable and therefore cannot be reassigned after initial assignment. Now it is more inclined to explicitly mutate these properties, and it can still be done by using the new Volatile class.

Let's quickly look at an example to demonstrate the new invariance constraint:

$task = new class extends Thread {
    private $response;

    public function run()
    {
        $content = file_get_contents("http://google.com");
        preg_match("~<title>(.+)</title>~", $content, $matches);
        $this->response = $matches[1];
    }
};

$task->start() && $task->join();

var_dump($task->response); // string(6) "Google"
Copy after login
Copy after login
Copy after login

On the other hand, the Threaded property of the Volatile class is mutable:

class Task extends Threaded
{
    public $response;

    public function someWork()
    {
        $content = file_get_contents('http://google.com');
        preg_match('~<title>(.+)</title>~', $content, $matches);
        $this->response = $matches[1];
    }
}

$task = new Task;

$thread = new class($task) extends Thread {
    private $task;

    public function __construct(Threaded $task)
    {
        $this->task = $task;
    }

    public function run()
    {
        $this->task->someWork();
    }
};

$thread->start() && $thread->join();

var_dump($task->response);
Copy after login
Copy after login
Copy after login

We can see that the Volatile class overrides the invariance enforced by its parent class Threaded class to allow reallocation (and unsetting) the Threaded property.

About variability and Volatile classes, there is another last basic topic that needs to be introduced - arrays. When an array is assigned to a property of the Threaded class, the array in pthreads is automatically cast to a Volatile object. This is because it is not safe to manipulate arrays from multiple contexts in PHP.

Let's quickly look at an example again to better understand:

<code>Threaded (implements Traversable, Collectable)
    Thread
        Worker
    Volatile
Pool</code>
Copy after login
Copy after login

We can see that Volatile objects can be treated like arrays, because they provide support for subset operators ([]) for array-based operations (as shown above). However, the Volatile class is not supported by common array-based functions such as array_pop and array_shift. Instead, the Threaded class provides us with these operations as built-in methods.

As a demonstration:

class Task extends Threaded
{
    private $value;

    public function __construct(int $i)
    {
        $this->value = $i;
    }

    public function run()
    {
        usleep(250000);
        echo "Task: {$this->value}\n";
    }
}

$worker = new Worker();
$worker->start();

for ($i = 0; $i < 15; $i++) {
    $worker->stack(new Task($i));
}

while ($worker->collect());

$worker->shutdown();
Copy after login

Other supported operations include Threaded::chunk and Threaded::merge.

Synchronization

The last topic that will be introduced in this article is synchronization in pthreads. Synchronization is a technology that allows control access to shared resources.

For example, let's implement a simple counter:

class Task extends Threaded
{
    private $value;

    public function __construct(int $i)
    {
        $this->value = $i;
    }

    public function run()
    {
        usleep(250000);
        echo "Task: {$this->value}\n";
    }
}

$pool = new Pool(4);

for ($i = 0; $i < 15; $i++) {
    $pool->submit(new Task($i));
}

while ($pool->collect());

$pool->shutdown();
Copy after login

If synchronization is not used, the output is not deterministic. Multiple threads write to a single variable without controlling access can result in lost updates.

Let's correct this by adding synchronization so that we get the correct output 20:

class Task extends Threaded // a Threaded class
{
    public function __construct()
    {
        $this->data = new Threaded();
        // $this->data is not overwritable, since it is a Threaded property of a Threaded class
    }
}

$task = new class(new Task()) extends Thread { // a Threaded class, since Thread extends Threaded
    public function __construct($tm)
    {
        $this->threadedMember = $tm;
        var_dump($this->threadedMember->data); // object(Threaded)#3 (0) {}
        $this->threadedMember = new StdClass(); // invalid, since the property is a Threaded member of a Threaded class
    }
};
Copy after login

Sync code blocks can also work with Threaded::wait and Threaded::notify (and Threaded::notifyOne).

The following are the interleaved increments from two synchronous while loops:

class Task extends Volatile
{
    public function __construct()
    {
        $this->data = new Threaded();
        $this->data = new StdClass(); // valid, since we are in a volatile class
    }
}

$task = new class(new Task()) extends Thread {
    public function __construct($vm)
    {
        $this->volatileMember = $vm;

        var_dump($this->volatileMember->data); // object(stdClass)#4 (0) {}

        // still invalid, since Volatile extends Threaded, so the property is still a Threaded member of a Threaded class
        $this->volatileMember = new StdClass();
    }
};
Copy after login

You may have noticed that additional conditions are added around the call to Threaded::wait. These conditions are critical because they only allow synchronous callbacks to recover when notification and specifies that the condition is true. This is important because notifications may come from outside the Threaded::notify call. Therefore, if the call to Threaded::wait is not included in the condition, we will be vulnerable to the false wake-up call , which will cause the code to be unpredictable.

Conclusion

We have seen five classes (Threaded, Thread, Worker, Volatile, and Pool) that come with pthreads, including introducing the usage of each class. We also looked at the new concept of invariance in pthreads, as well as a quick look at the synchronization features it supports. With these basics covered, we can now start looking at applying pthreads to some practical use cases! This will be the subject of our next post.

At the same time, if you have any application ideas for pthreads, feel free to leave your thoughts in the comment section below!

FAQs (FAQ) on Parallel Programming with Pthreads in PHP

What are the prerequisites for using Pthreads in PHP?

To use Pthreads in PHP, you need to have working knowledge of PHP and object-oriented programming. You also need to install ZTS (Zend Thread Safety) enabled PHP. Pthreads is not available in a standard PHP installation; it requires a thread-safe version of PHP. You can check if your PHP installation has ZTS enabled by running the command "php -i | grep "Thread Safety"" in the terminal. If it returns "Thread Safety => enabled", then you can use Pthreads.

How to install Pthreads in PHP?

To install Pthreads, you need to use PECL, which is the PHP extension community library. First, make sure you have ZTS enabled PHP installed. Then, in your terminal, run the command "pecl install pthreads". If the installation is successful, you need to add the line "extension=pthreads.so" to your php.ini file. This will load the Pthreads extension every time PHP is run.

How to create a new thread in PHP using Pthreads?

To create a new thread, you need to define a class that extends the Thread class provided by Pthreads. In this class you will override the run() method, which is the code that will be executed in a new thread. You can then create an instance of this class and call its start() method to start a new thread.

How to use Pthreads to share data between threads in PHP?

Pthreads provides the Threaded class for sharing data between threads. You can create a new instance of this class and pass it to your thread. Any properties set on this object will be securely shared between threads.

How to deal with errors in Pthreads?

Error handling in Pthreads is similar to error handling in standard PHP. You can use the try-catch block to catch exceptions. However, note that each thread has its own scope, so exceptions in one thread will not affect other threads.

Can I use Pthreads in PHP frameworks such as Laravel or Symfony?

Pthreads is incompatible with PHP frameworks such as Laravel or Symfony. This is because these frameworks are not designed to be thread-safe. If you need to perform parallel processing in these frameworks, consider using other techniques such as queues or asynchronous tasks.

How to debug PHP scripts using Pthreads?

Debugging PHP scripts using Pthreads can be challenging because each thread runs in its own context. However, you can use standard debugging techniques such as recording or outputting data to the console. You can also use PHP debuggers like Xdebug, but be aware that not all debuggers support multithreaded applications.

Can I use Pthreads in a web server environment?

Pthreads is not recommended in web server environments. It is designed for CLI (command line interface) scripts. Using Pthreads in a web server environment can lead to unpredictable results and is often unsafe.

How to use Pthreads to stop a running thread in PHP?

To stop a running thread, you can use the kill() method provided by Pthreads. However, this method should be used with caution, as it can lead to unpredictable results if the thread is in operation. It is usually best to design your threads so that they can complete their tasks cleanly.

Is there an alternative to Pthreads for parallel programming in PHP?

Yes, there are several alternatives to Pthreads for parallel programming in PHP. These include forks, a PECL extension that provides interfaces for creating and managing child processes, and parallel, a native PHP extension introduced in PHP 7.2, which provides a simpler and safer parallel programming interface.

The above is the detailed content of Parallel Programming with Pthreads in PHP - the Fundamentals. For more information, please follow other related articles on the PHP Chinese website!

Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Latest Articles by Author
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template