Skip to content

Add safe PHP multithreading with isolated worker VMs #68

Description

@TrixNEW

Summary

zphp already uses native threads internally for zphp serve, but normal PHP applications still have no general-purpose way to execute PHP code in parallel across multiple CPU cores.

It would be useful to expose a safe threading API based on:

1 native thread = 1 isolated PHP VM

The goal should not be to make a single VM concurrently accessible from multiple threads.

Instead, zphp should provide general-purpose primitives that any PHP library/framework/application can build threading systems on top of.

Proposed model

Main Thread
└── PHP VM #0
      |
      +--> Worker Thread #1 -> PHP VM #1
      +--> Worker Thread #2 -> PHP VM #2
      +--> Worker Thread #3 -> PHP VM #3

Each worker should:

  • own its VM for its entire lifetime
  • remain persistent between tasks
  • execute one task at a time
  • communicate through safe value transfer/message passing
  • never directly access another VM's mutable PHP state

This avoids needing locks/atomics throughout normal PHP objects, arrays, reference counting, GC, etc.

Example API

Something like:

$pool = new ZPHP\Parallel\Pool(4);

$future = $pool->submit(
    'expensiveFunction',
    [$data]
);

// main application can continue doing work

$result = $future->await();

$pool->shutdown();

Useful primitives could include:

$future->isComplete();
$future->await();
$future->cancel();

$pool->submit(...);
$pool->trySubmit(...);
$pool->shutdown();

The exact API isn't important; the underlying runtime support is.

Persistent workers

Workers should not create a new VM/thread for every task.

ThreadPool
├── Thread + persistent VM
├── Thread + persistent VM
├── Thread + persistent VM
└── Thread + persistent VM

Worker initialization should also be supported so applications can bootstrap things such as autoloaders or reusable worker-local state once.

For example:

$pool = new Pool(
    workers: 4,
    bootstrap: __DIR__ . '/worker.php'
);

Value transfer

Ordinary PHP objects/pointers should not be shared directly between VMs.

Initially, transferable types could include:

  • null
  • bool
  • int
  • float
  • string
  • arrays containing transferable values

Values should be copied/serialized into memory owned by the destination VM.

Large binary workloads could later use a transferable buffer where ownership is moved instead of copied.

Futures and completion

Submitted tasks should return a future.

Exceptions thrown inside a worker should propagate through that future rather than killing the worker:

try {
    $result = $future->await();
} catch (Throwable $e) {
    // worker task failed
}

Applications with event loops should also be able to check or receive completed tasks without busy-waiting.

Fiber integration could eventually allow await() to suspend a Fiber rather than block the OS thread.

Channels

A general-purpose channel primitive would also be useful:

$channel->send($value);
$value = $channel->receive();

This would allow applications to build their own:

  • worker pools
  • actors
  • async task systems
  • background workers
  • server architectures

without unsafe shared PHP memory.

Backpressure

Task/message queues should be bounded.

For example:

new Pool(
    workers: 4,
    queueCapacity: 1024
);

This prevents an application from submitting work faster than workers can consume it and exhausting memory.

Extensions

Extensions should have clear lifecycle boundaries for multithreaded execution:

module init
worker init
task execution
worker shutdown
module shutdown

Worker-local extension state should only be accessed from its owning thread.

Process-global mutable extension state would remain the extension author's responsibility to synchronize.

Safety requirements

The implementation should avoid:

  • sharing a VM between threads
  • global VM locks
  • unbounded queues
  • thread creation per task
  • VM creation per task
  • forced thread termination
  • executing PHP while internal queue locks are held

It should safely handle:

  • exceptions
  • cancellation
  • worker failures
  • shutdown while busy
  • queue saturation
  • worker initialization failures
  • repeated pool creation/destruction

There should be no unresolved futures, deadlocks, leaks, use-after-free, or allocator ownership violations.

Performance

CPU-heavy tasks should genuinely execute simultaneously on multiple cores.

Programs that do not use threading should ideally have effectively no additional synchronization overhead.

Benchmarks should cover:

1 / 2 / 4 / 8 workers
task submission latency
CPU-bound scaling
large value transfer
memory usage
shutdown under load

Possible implementation phases

Phase 1

  • persistent native worker threads
  • isolated VM per worker
  • named/static callable execution
  • transferable primitive values/arrays
  • futures
  • exception propagation
  • bounded task queue
  • worker bootstrap
  • safe shutdown

Phase 2

  • channels
  • cancellation/timeouts
  • completion queues
  • Fiber integration
  • worker recovery
  • better compiled-code reuse

Phase 3

  • zero-copy transferable buffers
  • richer transferable types
  • native extension task integration

Acceptance criteria

This:

$pool = new ZPHP\Parallel\Pool(4);

$futures = [];

for ($i = 0; $i < 100; ++$i) {
    $futures[] = $pool->submit('expensiveCalculation', [$i]);
}

foreach ($futures as $future) {
    echo $future->await(), PHP_EOL;
}

$pool->shutdown();

should:

  • execute PHP across multiple CPU cores
  • use isolated persistent VMs
  • safely transfer arguments/results
  • propagate exceptions
  • use bounded queues
  • shut down cleanly
  • avoid global VM locking
  • not negatively affect normal single-threaded PHP

zphp already has native threading and persistent worker VMs for server mode, so exposing similar infrastructure as a safe general-purpose PHP parallelism API could make zphp useful for much more than request-based workloads.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions