Ubuntu 16.04: How to install ZTS enabled PHP7 and the pThreads module

Working with cutting-edge tech at RapidSpike our dev team often faces tricky, involved technical problems.

Our solutions to these issues might be of use to other developers – so we’ve asked the team to share their work on our blog. If you’re a developer and this post has helped you – let us know!

 

To achieve asynchronous processing with PHP, first you must compile PHP with Zend Thread Safety enabled. The easiest way to do this is to install a pre-ZTS enabled PHP package called: php7.0-zts. This can be found in a PPA provided by Ondřej Surý, but be warned – its experimental and unsupported, so use with care. Another method is to build PHP manually and enable ZTS in the configuration; this Stackoverflow answer is a good place to start.

For the purpose of this tutorial, I will use the php7.0-zts package, however, I would recommend only using this method for building proof of concepts and exploring async PHP.

Once ZTS PHP is installed you must then install a module called pThreads which adds async functionality to PHP natively.

 

Installation

Prerequisite: a Virtual Machine or server running a clean install of Ubuntu 16.04.

1. Add the package repository

sudo add-apt-repository ppa:ondrej/php-zts
sudo apt-get -y update

2. Install ZTS enabled PHP

pThreads won’t install without the php7.0-zts-xml and php7.0-zts-dev packages, so these must also be installed:

sudo apt -y install php7.0-zts php7.0-zts-common php7.0-zts-xml php7.0-zts-dev

3. Check ZTS is enabled

This should return an integer of ‘1’.

php -r "echo PHP_ZTS;"

4. Install pThreads via Pecl

sudo apt install php-pear
sudo pecl install pthreads
sudo echo "extension=pthreads.so" >> /etc/php/7.0-zts/mods-available/pthreads.ini
sudo echo "extension=pthreads.so" >> /etc/php/7.0-zts/cli/conf.d/pthreads.ini

5. Check pThreads is installed

This should return an integer of ‘1’.

php -r "print_r(class_exists('Thread'));"

These steps will build PHP with access to the `Thread` class, which in turn gives access to async PHP! Now the fun part…

 

Testing

This code example – edited, but originally taken from here – is a good place to start understanding how pThreads works. It simply creates a loop and spawns child processes designed to take a random amount of time to complete. Once they complete the result is reported.

#!/usr/bin/php
<?php 
class AsyncOperation extends Thread { 
    public function __construct($arg) 
    {
        $this->arg = $arg;
    }

    public function run()
    {
        if ($this->arg) {
            $sleep = mt_rand(5, 10);
            printf('%s: %s  -start -sleeps %d' . "\n", date("g:i:sa"), $this->arg, $sleep);
            sleep($sleep);
            printf('%s: %s  -finish' . "\n", date("g:i:sa"), $this->arg);
        }
    }
}

$ops = array();
foreach (range("A", "Z") as $k => $task) {
    $ops[$task] = new AsyncOperation($task);
    $ops[$task]->start();
}

This is great, but there is no control over how many processes will be run at once. Therefore it would be good to introduce a ‘concurrency’ setting and use this to limit how many tasks are being run.

The following example achieves this by limiting itself to a given concurrency. Further improvements can be made; adding timeouts to the processes and killing old processes would mean the task pool is better maintained. But this is a good starting place for a proof of concept.

#!/usr/bin/php
<?php 
class AsyncOperation extends Thread { 
    public function __construct($arg) 
    {
        $this->arg = $arg;
    }

    public function run()
    {
        if ($this->arg) {
            $sleep = mt_rand(5, 10);
            printf('%s: %s  -start -sleeps %d' . "\n", date("g:i:sa"), $this->arg, $sleep);
            sleep($sleep);
            printf('%s: %s  -finish' . "\n", date("g:i:sa"), $this->arg);
        }
    }
}

// Create a tasks list
$tasks = range("A", "Z");

// Concurrency setting, exit status and operations array
$conc = 2;
$exit = false;
$ops = array();

while (!$exit) {
    foreach ($tasks as $k => $task) {
        // If the current operations count is less than concurrency setting, add a new task to the pool
        $pool = count($ops);
        if ($pool >= $conc) {
            sleep(1);
            break;
        } else {
            $ops[$task] = new AsyncOperation($task);
            $ops[$task]->start();
            unset($tasks[$k]);
        }
    }

    foreach ($ops as $task => $op) {
        if (!$op->isRunning()) {
            // Op finished - remove from pool
            printf('%s: %s  -exited' . "\n", date("g:i:sa"), $task);
            unset($ops[$task]);
        }
    }

    if (empty($tasks) && empty($ops)) {
        $exit = true;
    }
}

 

Conclusion

These examples will give you a good idea of how powerful pThreads can be for processing multiple tasks at once using PHP. However, installing PHP with ZTS enabled does have its drawbacks. For example, PHP’s performance is impacted and its installation methods are not ideal. Using experimental package repository is not recommended for a production environment and compiling PHP manually is not for the faint hearted.

 

Extracurricular

If you want to install other PHP packages using the package manage then you have to install the ZTS versions from the custom package repo. For example to install curl run this;

sudo apt install php7.0-zts-curl

To check what packages are available to you run:

sudo apt search php7.0-zts