True PHP7 Multi-Threading: How to Rebuild PHP and use pthreads

Building PHP on Mac and Linux with Multi-Threading and pthreads examples

Your PHP apps could really do with multi-threading capabilities for running tasks in parallel, but you know the PHP building process can be troublesome and time consuming — not only this, migrating your production server PHP to a new build also sounds like a headache. Well, things are not all bad if we utilise a couple of PHP utilities at our disposal to aid the process.

This article will walk you through this process of compiling a new, multi-threading capable PHP build, before migrating to that version, then demonstrate how to code multi-threaded applications with the pthreads extension.

Off-the-shelf PHP builds from package managers do not support multi-threading. What we need to do is rebuild PHP with a flag that enables ZTS, or “Zend Thread Safety”, which then allows us to expand our thread use. To facilitate this build process we will utilise a tool named phpbrew, designed to build multiple versions of PHP and switch between them with ease. Then, only once our builds are completed can we experiment with pthreads.

There are some platform specific gotchas, but we will overcome these with simple solutions.

You understandably have a development machine and production ready servers, therefore we will go through the phpbrew building process on both Mac OS and CentOS. If you are using another distribution I will highlight a resource for specific OS requirements.

Let’s start with building PHP on a Mac.

phpbrew Introduction

phpbrew has been around since 2012 and is still being bumped to this day — some committed maintainers demonstrating that PHP is still a critical tool for a lot of developers. The installation and usage instructions of phpbrew are coherent and easy to understand. Although not required, it’s beneficial to familiarise yourself with phpbrew at this point.

Why use phpbrew over a vanilla build command?

For a few reasons. Not only can phpbrew build multiple versions of PHP and allow us to easily switch between them, it also makes the build process a lot easier with the use of “Variants”. Instead of copying many flags as we would in a vanilla build command, we can utilise keywords that act as either one flag or a group of flags. An example of a Variant is +mb, which will build PHP with mbstring and mbregex. Go directly to the list of variants here to see what is available.

Beyond Variants, we can install extensions and configure our ini file with only one command. In other words, we don’t have to worry about maintaining the ini after installing an extension — phpbrew does that for us. For example, we will be utilising the following command to install pthreads into our newly built PHP version:

phpbrew ext install github:krakjoe/pthreads

As you can see, we are installing the latest version (important!) of the pthreads extension directly from Github and including it in our configuration file, all from this command. Pretty nice. This is part of the installing extensions support phpbrew offers.

Their Github link to the project home page is here:

Using phpbrew on Mac OS

Let’s install phpbrew first and foremost. Within Terminal, download and install phpbrew with the following commands:

curl -L -O https://github.com/phpbrew/phpbrew/raw/master/phpbrew
chmod +x phpbrew
sudo mv phpbrew /usr/local/bin/phpbrew

After moving phpbrew into your local bin, initialise a bash script for your shell environment:

phpbrew init
[[ -e ~/.phpbrew/bashrc ]] && source ~/.phpbrew/bashrc

Now run the following commands to update the PHP builds available to phpbrew:

phpbrew known
phpbrew update

We will be building the latest version of PHP7.2, being 7.2.12 at the time of this writing. Feel free to use a newer release if you have access to it.

At this point you could also run phpbrew variants for a full list of available Variants at your disposal.

phpbrew is now ready to use — and only one command is now needed to build PHP. But we have one more thing to do — install the PHP dependencies.

Mac: Installing PHP dependencies

PHP has a lot of requirements, and phpbrew have dedicated a whole page of their documentation to outline platform specific installation instructions for these requirements.

We are using a Mac at this point, and according to the above Requirement page’s Mac OS section, we can either use HomeBrew or MacPorts to install them.

Take my advice and use MacPorts. I firstly (and eagerly) attempted to use HomeBrew, but ran into consistent linking failures and build errors with no resolution in sight. If you have not got MacPorts installed, grab the latest version for your OS (10.14 Mojave being the latest at this time) from the MacPorts Downloads page. Once installed, run the following commands:

port install curl automake autoconf icu depof:php72 depof:php72-gd mcrypt bison re2c gettext openssl

Some of these may already be installed on your system. That is not a problem — just run the entire command and let the installer do the rest.

Mac: Building PHP

We are now ready to build PHP with ZTS enabled. Run the following phpbrew install command to do so:

phpbrew install php-7.2.12 +default -- --enable-maintainer-zts

We are using the +default Variant here, which includes commonly used PHP extensions. We are also extending our variants with plain flags, which can be appended to the command with -- --flag1 --flagN.

This process may take a while depending on your machine. On a first generation 12" MacBook that I tested this build on, the build completed in just over 30 minutes. A build log is provided as it is happening that you can tail, giving you real time feedback.

Once the build is completed we are greeted with outputs of where the build is located, along with our ini configuration file location. Very handy.

Mac: Using the New Build

At this point you can run phpbrew list listing all your PHP builds. You will notice php-7.2.12 on that list — the version just built.

phpbrew also provides the switch and use commands to switch PHP version, dependent on our builds. Permanently switch to the new build like so:

phpbrew use php-7.2.12

Within the Terminal window you are in now, the php command will always refer to the newly built PHP7.2.

Mac: Installing pthreads

Our last job is to install the pthreads extension. Do so with the following command:

phpbrew ext install github:krakjoe/pthreads

Let’s consider a couple of other extensions you may wish to install. I use Composer and MongoDB, and also Xdebug on my development environment. To install these extensions, run the following:

phpbrew ext install mongodb
phpbrew ext install xdebug stable
phpbrew app get composer

If you require any other extension, go ahead and use phpbrew ext install to get your build up to date with what you need.

At this point we are ready to use pthreads on a Mac. In the next section we will run through the installation process on CentOS. Skip ahead to the pthreads section if you do not need further installations!

Using phpbrew on Cent OS7

Let’s run through installing the dependencies for building PHP on CentOS first. Install the requirements with the following comamnds:

sudo yum install make automake gcc gcc-c++ kernel-devel
sudo yum install php php-devel php-pear bzip2-devel yum-utils bison re2c libmcrypt-devel libpqxx-devel libxslt-devel pcre-devel libcurl-devel libgsasl-devel openldap-devel, httpd-devel

I also needed to install readline on my CentOS system:

yum install readline-devel

If you do not have PHP installed on your CentOS server, the phpbrew Requirements recommend you install it firstly. I prefer using RPM to install PHP. Do so with the following comamnds:

sudo yum -y install epel-release
wget http://rpms.famillecollet.com/enterprise/remi-release-7.rpm
rpm -Uvh remi-release-7*.rpm

Update the PHP7.2 remi file to enable php7.2. Change enabled=0 to enabled=1 in the [remi-php7.2] block only:

cd /etc/yum.repos.d/
#open php7.2 repo file and change enabled=0 to enabled=1
sudo vi remi-php72.repo

Finally, install the following PHP packages using yum:

sudo yum install php php-gd php-common php-mysql php-mcrypt php-devel php-xml

CentOS: Installing phpbrew

Installing phpbrew is identical to the Mac. For completeness, the installation commands are listed here again:

curl -L -O https://github.com/phpbrew/phpbrew/raw/master/phpbrew
chmod +x phpbrew
sudo mv phpbrew /usr/local/bin/phpbrew
phpbrew init
[[ -e ~/.phpbrew/bashrc ]] && source ~/.phpbrew/bashrc
phpbrew known
phpbrew update

It is worth noting that if you reboot your server you will need to re-inititate phpbrew with phpbrew init and amend bashrc again. You may wish to set up a daemon at boot time to do this automatically after finishing the article.

CentOS: Building PHP

Now, the build command differs slightly from that of the Mac. On CentOS, build PHP with:

phpbrew install php-7.2.12 +default +openssl=/usr -- --enable-maintainer-zts --with-libdir=lib64

We are now using the +openssl=/usr Variant and and additional—-with-libdir=lib64 flag. Why is this? Because unlike the Mac build, we explicitly need to define the OpenSSL location for the PHP build to compile. If not, we will get an error during the build process stating no OpenSSL libraries were found. This oversight requires a simple fix, but the issue has been discussed on Github here if you would like to investigate it.

Note on memory requirements building PHP on VPSs: If you are using a VPS make sure you have at least 1GB of free memory. I have attempted to build PHP on around 400mb of free memory — which ran out and consequently the build failed. If you need to, upgrade your VPS resources temporarily to build PHP, then revert back once completed.

CentOS: Using the New Build

With a successful new build, we can now use it in an identical way to the Mac process:

phpbrew use php-7.2.12

CentOS: Installing pthreads

Installing the pthreads extension — and the others of interest — is also identical to the Mac:

phpbrew ext install github:krakjoe/pthreads
phpbrew ext install mongodb
phpbrew ext install xdebug stable
phpbrew app get composer

With this section completed, we now have a suitable PHP build for multithreading on a local machine and production environment! The last piece of the puzzle is using pthreads to realise our new found threading capabilities.

Using pthreads

pthreads brings PHP’s threading capability to what developers now take for granted in other languages. It provides multi-threading that is compatible with PHP based on Posix Threads. This is true multi threading at its core.

So how do we use pthreads? Well, it provides a class named Threads that we extend from. From here, we utilise Thread’s run() method to code the logic we wish to carry out in a separate thread. Think something like this:

class MyProgram extends Thread {
   public function run() {
      //run my program
      echo "Woo, I am running in a new thread!";
}
}
$program1 = new MyProgram;
$program1->start() && $program1->join();
echo 'Thread finished';

Here we are utilising two pthread methods start() and join(), which are inherited from Thread.

start() is self explanatory — it calls the run() method in a separate thread, allowing for our parallel processing to begin.

join() on the other hand requires a little more explanation:

join() allows us to bring a thread into the current context, which therefore wait for it to finish executing before our current context continues executing.

In other words, if we join a thread to our main process, the main process will wait for our joined thread to finish before it continues executing.

In the snippet of code above, $program1’s thread is joined to our main process, therefore our last echo statement is called after the thread has finished processing.

Example 1: Successively Joining Threads

To understand what is happening here, consider the following example where 5 thread jobs are instantiated, started and joined in order. Each thread process sleeps for X seconds (X being the thread index) before echoing a closing statement:

Because we are bringing each thread into context as it is started, the execution will wait until said thread has finished executing before moving onto the next one.

Within your phpbrew Terminal window, run the above script to examine this behaviour, with php multi-thread1.php.

Example 2: Running Threads Simultaneously

Let’s take the same class and behaviour as above, but this time not join each thread as they start.

In this example all 5 threads are started simultaneously in our loop, with the join() method removed. Our main execution will then sleep for 5 seconds as the threads finish processing in the background. We will then try to join the threads again. Run the following example to examine the behaviour:

A couple of interesting points to note here:

  • Even though the threads are running simultaneously in separate threads, we are still receiving their echo statements within the Terminal.
  • We attempt to join the threads once they finish executing — which fails. Instead of PHP throwing an error, execution simply continues on our main process, as demonstrated by the last echo statement.

Can Threads be used in namespace classes?

Yes they can indeed. If you are using Composer autoloading for example, you can simply extend your classes with extends \Thread as per our previous examples.

Exploring pthreads

The best way to explore the pthreads library is via the php.net documentation. In total, pthreads consists of 9 classes and 53 methods at the time of writing. In reality you may not need to use the majority of these. In fact if you simply wish to speed up execution by running parallel tasks, the methods used in the examples above will suffice for you. However, it is always worth familiarising yourself with the capabilities of a package for future reference.

Take a look at the pthreads documentation index to get started. All the methods listed there come with examples, likeThread::getCurrentThreadId — an identification function that can be called within the run() method of a thread to get its ID.

The Threaded class contains valuable state detection methods, such as isRunning, another useful method allowing us to check whether the run() method is executing at a given time on a particular thread.


To Conclude

You should now be ready to upgrade your PHP apps to a multi-threaded setup. It is very satisfying to unlock this added capability and see your scripts performing faster. Much faster.

Take some time to design your threads in such a way that maximises speed, but to execute tasks in a clean order. Do some threads depend on the completion of others? If so, keep track of your execution state. Remember that threads add flexibility and speed, but they also add complexity to your apps.

How are you using pthreads? It would be great to hear in the Responses!