byjg / phpthread
ByJG PHPThread simplifies working with threads and non-blocking code in PHP, bridging the gap for a language that was not inherently designed for threading.
Requires
- php: >=8.3 <8.6
- ext-pcntl: *
- ext-posix: *
- byjg/cache-engine: ^6.0
Requires (Dev)
- byjg/restserver: ^6.0
- phpunit/phpunit: ^10.5|^11.5
- react/event-loop: ^1.5
- react/promise: ^3.2
- vimeo/psalm: ^5.9|^6.13
Suggests
- ext-pthreads: *
Provides
None
Conflicts
None
Replaces
None
README
| sidebar_key | phpthread | ||
|---|---|---|---|
| tags |
|
ByJG PHPThread: Simplified Threads and Non-Blocking Code in PHP
ByJG PHPThread simplifies working with threads and non-blocking code in PHP, bridging the gap for a language that was not inherently designed for threading.
Table of Contents
Overview
PHPThread is a polyfill library that abstracts threading functionality for PHP, providing a consistent interface regardless of the underlying PHP setup (ZTS or Fork). It empowers developers to implement thread-like behavior and asynchronous processing in PHP applications.
Why Threads in PHP?
PHP is traditionally designed for a request-response cycle, where scripts are executed only in response to client requests and terminate after sending a response. While efficient for web applications, this architecture lacks native threading support for background or concurrent tasks.
With PHPThread, you can overcome these limitations by leveraging:
- Forking (Default PHP) for simulating threading.
- Zend Thread Safety (ZTS) for true multi-threading environments.
- Child PHP processes (
proc_open) outside the CLI, e.g. under PHP-FPM.
How It Works
Default PHP (Non-ZTS)
In standard PHP installations (without ZTS), threading is simulated using the fork command.
This approach creates a new process by cloning the parent process. While not a true thread,
this method can approximate threading behavior.
Requirements:
- pcntl and posix: For process control.
They are optional (
suggestincomposer.json): without them theProcessHandleris used. - A tmpfs mounted at
/dev/shm: For sharing results between processes.
PHP ZTS (Zend Thread Safety)
With ZTS-enabled PHP, true multi-threading becomes possible. This setup is ideal for production environments where
robust threading is required. The ZTS version is compiled with the --enable-zts flag, but it may not be included in
all PHP distributions.
Requirements:
- parallel: For multi-threading functionality.
Outside the CLI
Forking and parallel only work in the CLI. Outside it, the ProcessHandler runs each closure in a child
PHP CLI process started with proc_open() (the approach of amphp/parallel). The closure is serialized with
laravel/serializable-closure, so it, its arguments and its result
must be serializable (no resources). See ProcessHandler.
Requirements:
proc_open()enabled and a PHP CLI binary (found automatically, or set withProcessHandler::setPhpBinary()).- A tmpfs mounted at
/dev/shm: used by promises.
Features
- Thread Management: Simplified thread creation and execution (docs).
- Thread Pools: Efficiently manage and reuse threads for multiple tasks (docs).
- Promises: Truly asynchronous and non-blocking task management with a JavaScript-like Promise API (docs, benchmark).
Supported Promise Methods:
then(): Execute a callback on promise resolution (and, optionally, on rejection).catch(): Execute a callback on promise rejection; the returned promise resolves with its return value.finally(): Execute a callback after resolution or rejection; the original value or reason is passed through.Promise::resolve(): Resolve a promise.Promise::reject(): Reject a promise.Promise::all(): Wait for all promises to resolve.Promise::race(): Wait for the first promise to resolve.
Limitations
See also Limitations.
Parallel Execution Outside the CLI
:::caution Important
In web server environments such as PHP-FPM, Apache with mod_php or FrankenPHP, pcntl_fork() and parallel
cannot be used. There Thread::create() returns a ProcessHandler: the closure runs in parallel in a child PHP
CLI process, so it must be serializable (see ProcessHandler for the limits).
When proc_open() is disabled or no PHP CLI binary is found, it falls back to the SyncHandler: the closure runs
synchronously in the current process when start() is called. The code keeps working (threads, thread pools
and promises return the same results), but there is no parallelism: the work is done one task after another.
:::
To know which handler is used, call Thread::handlerClass() (the class Thread::create() returns in the current
environment) or getClassName() on the created thread:
use ByJG\PHPThread\Handler\SyncHandler; use ByJG\PHPThread\Thread; if (Thread::handlerClass() === SyncHandler::class) { // No parallelism here: ParallelHandler, ForkHandler or ProcessHandler are not available }
Thread::useHandler(SyncHandler::class) forces the synchronous execution (e.g. in tests, or for a deterministic
run), and Thread::useHandler(null) restores the automatic choice.
Why?
- pcntl extension (used for forking): forking a web server worker would interfere with the server's own process management, so the library only forks in the CLI.
- parallel extension (used for true threading): requires a ZTS build and the CLI.
- proc_open (used by the
ProcessHandler) starts a separate PHP CLI process, which is safe in any SAPI, at the cost of starting PHP (and the Composer autoloader) for each thread.
Use Cases:
This library is designed for:
- CLI scripts and daemons
- Background job processors
- Command-line tools
- Long-running CLI applications
- Scheduled tasks (cron jobs)
- Running a few independent tasks in parallel inside a web request
For long or many background tasks in web applications, consider using message queues (RabbitMQ, Redis, etc.) with background workers running in CLI mode.
Forking and Data Sharing
When simulating threads with fork, data cannot be returned directly from the child process
to the parent. The library serializes the results to the tmpfs filesystem (/dev/shm) to share them between processes.
However:
- Avoid returning large or complex objects, as this may cause memory exhaustion.
Installation
Requirements
Non-ZTS (Default PHP)
- PHP ≥8.3
pcntlandposixextensions (optional: without them theProcessHandlerruns the threads)- tmpfs mounted at
/dev/shm
Outside the CLI (PHP-FPM, ...)
- PHP ≥8.3
proc_open()enabled and a PHP CLI binary installed next to the server one- tmpfs mounted at
/dev/shm(for promises)
ZTS (Zend Thread Safety)
- PHP ≥8.3 compiled with
--enable-zts parallelextensionpcntlandposixextensions (optional)
Install via Composer
composer require byjg/phpthread
Running Tests
docker compose up -d vendor/bin/phpunit