What is thread safe or non-thread safe in PHP?

Viewed 421083

I saw different binaries for PHP, like non-thread or thread safe?

What does this mean?

What is the difference between these packages?

5 Answers

The other answers address SAPIs implementations, and while this is relevant the question asks the difference between the thread-safe vs non thread-safe distributions.

First, PHP is compiled as an embeddable library, such as libphp.so on *NIX and php.dll on Windows. This library can be embedded into any C/CPP application, but obviously it is primarily used on web servers. At it's core PHP starts up in in two major phases, the module init phase and then request init phase. Module init initializes the PHP core and all extensions, where request init initializes PHP userspace - both native userspace features as well as PHP code itself.

The PHP library is setup to where the module phase only has to be called on once, but the request phase has to be reinitialized for each HTTP request. Note that CLI links to the same library as mod_php ect, and still has to go through these phases internally even though it may not be used in the context of processing HTTP requests. Also, it's important to note that PHP isn't literally designed for processing HTTP requests - most accurately, it is designed for processing CGI events. Again, this isn't just php-cgi, but all SAPI/applications including php-fpm, mod_php, CLI and even the exceedingly rare PHP desktop application.

Webservers (or more typically SAPIs) that link to libphp tend to follow one of four patterns:

  • create a completely new instance of a PHP per request (old CGI pattern, not common and obviously not recommended)
  • create one PHP instance, but go though both initialization phases (together) in separate forked child processes
  • create one PHP instance, do module init once in parent process pre-fork, and then the individual request phase post-fork per HTTP request

Note that in examples 2 and 3 the child process is typically terminated after each request. In example 2 the child process must be terminated at the end of each request.

The forth example is related to threaded implementations

  • call module init once in main thread, then call request init within other threads.

In the threaded case, request handling threads tend to utilize a thread pool, with each thread running in a loop initializing the request phase at the beginning and than destroying the request phase at the end which is more optimal than spawning a new thread per request

Regardless of how threaded implementations utilize libphp, if the module phase is initialized in one thread and request phases are called in different threads (which is the case PHP was designed for) it requires a non-trivial amount of synchronization, not just within the PHP core, but also within all native PHP extensions. Note that this is not just a matter of a “request” at this point, but synchronization that it being called on per PHP OPCODE that relies on any form of resource within the PHP core (or any PHP extension) which exists in a different thread as PHP userspace.

This places a huge demand on synchronization within thread-safe PHP distributions, which is why PHP tends to follow a "share nothing" rule which helps minimize the impact, but there is no such thing as truly "sharing nothing" in this pattern, unless each thread contains a completely separate PHP context, where the module phase and request phase is all done within the same thread per request which is not suggested or supported. If the context built within the module init phase is in a separate thread as the request init phase there will most definitely be sharing between threads. So the best attempt is made to minimize context within the module init phase that must be shared between threads, but this is not easy and in some cases not possible.

This is especially true in more complicated extensions which have their own requirements of how a their own context must be shared between threads, with openssl being a major culprit of of this example which effectually extends outward to any extension that uses it, whether internal such as PHP stream handlers or external such as sockets, curl, database extensions, etc.

If not obvious at this point, thread-safe vs non thread-safe is not just a matter of how PHP works internally as a “request handler” within an SAPI implementation, but a matter of how PHP works internally as an embedded virtual machine for a programming language.

This is all made possible by the TSRM, or the thread safe resource manager, which is well made and handles a very large amount of synchronization with little perceived overhead, but the overhead is definitely there and will grow not just based on how many requests per second that the server must handle (the deciding factor on how may threads the SAPI requires), but also by how much PHP code is used per request (or per execution). In other words, large bloated frameworks can make a real difference when it comes specifically to TSRM overhead. This isn't to speak of overall PHP performance and resource requirements within thread-safe PHP, but just the additional overhead of TSRM itself within thread-safe PHP.

As such, pre compiled PHP is distributed in two flavors, one built where TSRM is active in libphp (thread-safe) and one where libphp does not use any TSRM features (non thread-safe) and thus does not have the overhead of TSRM.

Also note that the flag used to compile PHP with TSRM (--enable-maintainer-zts or --with-zts in later PHP versions) causes phpize to extend this outward into the compilation of extensions and how they initialize their own libraries (libssl, libzip, libcurl, etc) which will often have their own way of compiling for thread-safe vs non thread-safe implementations, i.e their own synchronization mechanisms outside of TSRM and PHP as a whole. While this not exactly PHP related, in the end will still have effect on PHP performance outside of TSRM (meaning on top of TSRM). As such, PHP extensions (and their dependents, as well as external libraries PHP or extensions link to or otherwise depend on) will often have different attributes in thead-safe PHP distributions.

Related