Can PHP Embrace Multithreading? Exploring TrueAsync, Offload Models, and Actor Architectures

This article examines the challenges and possibilities of adding multithreading to PHP, discussing the TrueAsync RFC, single‑threaded + offload patterns, memory‑manager redesign, object movement, shared objects, region‑based memory, and actor models to guide future PHP kernel evolution.

Open Source Tech Hub
Open Source Tech Hub
Open Source Tech Hub
Can PHP Embrace Multithreading? Exploring TrueAsync, Offload Models, and Actor Architectures

In the context of RFC TrueAsync 1.7, the author asks how the proposed changes will interact with possible future modifications to the PHP core and why anticipating PHP's direction is crucial for long‑term language design.

Motivation and Background

The TrueAsync project aims not only to modify the PHP kernel for asynchronous execution but also to explore broader questions:

How far can PHP go with multithreading?

Are there fundamental limits?

What kernel changes are required to make multithreading practical?

Which language abstractions become possible?

The author does not intend to cover every multithreading detail but hopes the discussion will be useful for many PHP developers and set a direction for future debates.

Historical Experiment

Initially, the author needed high‑volume telemetry in a PHP application and thought it impossible. After seeing Swoole’s architecture, they built an optimized OpenTelemetry implementation that batches data, compresses it, and serializes it with MessagePack. The hypothesis was that a single‑threaded coroutine could collect telemetry and periodically send it without cross‑thread interaction.

Experiments showed a 50 % throughput drop when using telemetry, revealing that CPU‑intensive compression overwhelmed the API despite Swoole’s non‑blocking I/O. A second version moved telemetry collection to a separate job process, improving performance but still relying on pipe‑based inter‑process communication.

Single‑Threaded + Offload Model

This model separates tasks into two categories:

I/O‑bound tasks – file reads, network calls, database access. Thousands of such operations can be handled concurrently with coroutines or await.

CPU‑bound tasks – compression, encryption, parsing, heavy calculations. These require multiple cores and cannot be efficiently handled by coroutines alone.

Physically, the main thread (the event loop) handles I/O, while dedicated worker threads or processes handle CPU‑bound work. The article cites Node.js Worker Threads and Python’s asyncio with run_in_executor or to_thread as analogous solutions.

Advantages

High resource utilization: event loops process thousands of I/O operations with minimal overhead, while workers achieve true parallelism on multiple cores.

Simpler development: single‑threaded code avoids mutexes and data races; workers follow a "shared‑nothing" principle.

Easier compiler/runtime support: asynchronous functions are simpler to implement than full multithreading support.

Explicit load distribution: developers can decide which parts run in the event loop and which run in workers.

Drawbacks

Manual load distribution can be error‑prone; mis‑classifying tasks leads to degraded responsiveness.

The model is not universal: it excels for web servers and APIs but is less suitable for compute‑heavy workloads like scientific computing or machine learning.

Is PHP Ready for Multithreading?

The author argues that multithreading is not needed for every use case and questions why PHP developers often assume threads are required for parallelism. They emphasize that processes already provide isolation and can communicate via IPC, so threads must offer a clear advantage.

Threads exist to transfer memory between tasks without extra overhead.

In PHP, the Shared Nothing principle means each thread has its own memory space, but safe sharing still requires careful design.

PHP Memory Model

PHP’s memory consists of code, data, and VM state. The global object_store and reference‑counted garbage collector are not thread‑safe. Consequently, PHP is effectively single‑threaded, and the GC must stop the world.

Moving the VM Between Threads

PHP uses thread‑local storage (TLS) to keep VM state per thread (ZTS mode). Low‑level tricks like __thread (or __declspec(thread) on Windows) retrieve the VM pointer from FS/GS registers. Moving only a subset of the VM (e.g., zend_executor_globals) could enable lightweight actors, but full VM migration incurs performance costs.

Cross‑Thread Object Transfer

To move an object safely, the article proposes a move‑semantic similar to C++/Rust:

$obj = new SomeObject();
$thread = new Thread(function () use ($obj) {
    echo $obj->someMethod();
});
$thread->join();

Only one thread may own the object; after the move, the original reference becomes undefined. The author outlines safety checks: full graph traversal, reference‑count verification, and identity preservation. A deep‑copy algorithm with O(N+E) time and O(N) space is presented, along with pseudo‑code for allocating memory in the target thread’s memory manager.

Cross‑Thread Deallocation

Each thread has its own memory manager (MM). When a thread encounters a pointer allocated by another thread, it sends a "free‑request" to the owning MM, which later releases the memory. This deferred‑free approach mirrors modern lock‑free allocators and enables high‑throughput cross‑thread deallocation.

Shared Objects and Region‑Based Memory

Immutable objects (e.g., interned strings, empty arrays, opcache constants) already use a GC_IMMUTABLE flag to skip refcount updates. Extending this idea with a GC_SHARE flag could create true shared objects, though it adds ~34 % overhead for refcount increments.

Region‑based memory allocates all objects for a task in a dedicated region that can be freed wholesale, simplifying GC and enabling efficient object movement between threads.

Cooperating Coroutines and Threads

In this model, a Thread is an Awaitable. Coroutines can await thread results without blocking the event loop. Example code shows creating a ThreadPool, enqueuing a CPU‑bound image generation task, and awaiting the result within a coroutine.

Actor Model for PHP

Actors encapsulate isolated state and process messages sequentially, running on separate threads. The article provides a ChatRoom actor example, showing how method calls are dispatched to a worker thread via an internal queue, with the coroutine suspending until the result is ready.

Actors eliminate data races, mutexes, and complex synchronization, offering a high‑level parallelism primitive that fits PHP’s OOP style. The implementation relies on a thread pool, MPMC queues, and a scheduler that allocates CPU time across actors.

Conclusion

The "single‑threaded + offload" model is expected to appear soon, as many components (beta coroutine engine, experimental multithreaded memory manager, thread API) are already in place. Full actor support remains a longer‑term goal for PHP 9, requiring extensive kernel changes but promising a safe, multithreaded programming experience.

Original source: https://github.com/true-async/multithreaded-php
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Memory Managementactor-modelTrueAsync
Open Source Tech Hub
Written by

Open Source Tech Hub

Sharing cutting-edge internet technologies and practical AI resources.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.