amphp/parallel
True parallel processing for PHP with AMPHP: run blocking tasks in worker processes or threads without blocking the event loop. Provides non-blocking concurrency tools and an easy worker pool API for distributing work; no extensions required (threads optional).
Installation:
composer require amphp/parallel
Ensure PHP 8.1+ is used. For threads, PHP 8.2+ with ZTS and ext-parallel is required.
First Use Case: Offload a blocking task (e.g., file I/O, CPU-heavy operations) to a worker:
use Amp\Parallel\Worker;
use Amp\Parallel\Worker\Task;
class MyTask implements Task {
public function run(\Amp\Sync\Channel $channel, \Amp\Cancellation $cancellation): string {
return file_get_contents('https://example.com'); // Blocking call
}
}
$worker = Worker::createWorker();
$execution = $worker->submit(new MyTask());
$result = $execution->await();
Key Files:
src/ for core classes (Worker, Task, WorkerPool).tests/ for integration patterns (e.g., cancellation, IPC).Task Submission:
Worker::submit() or WorkerPool::submit() for parallel execution.$pool = new WorkerPool(4); // 4 concurrent workers
foreach ($uploads as $upload) {
$pool->submit(new ProcessUploadTask($upload));
}
Worker Pools:
$pool = new WorkerPool(8, new ProcessContextFactory());
$pool->submit(new HeavyTask());
Worker\workerPool().IPC Patterns:
Channel:
$context = contextFactory()->start(__DIR__.'/child.php');
$context->send(['command' => 'fetch', 'url' => 'https://api.example.com']);
$response = $context->receive();
Cancellation:
$cancellation = new Cancellation();
$execution = $worker->submit(new MyTask(), $cancellation);
$cancellation->cancel(); // Triggers CancellationException in worker
Laravel Integration:
WorkerPool in Laravel’s app() container:
$this->app->singleton(WorkerPool::class, fn() => new WorkerPool(4));
class ParallelJob implements ShouldQueue {
public function handle() {
$worker = app(WorkerPool::class)->getWorker();
$worker->submit(new ProcessDataTask($this->data));
}
}
Error Handling:
try-catch:
try {
$result = $execution->await();
} catch (WorkerException $e) {
\Log::error('Worker failed:', ['error' => $e->getMessage()]);
}
Resource Sharing:
AtomicCache for thread-safe shared state:
class SharedTask implements Task {
private static AtomicCache $cache;
public function run(Channel $channel, Cancellation $cancellation) {
self::$cache ??= new AtomicCache();
return self::$cache->getOrSet('key', fn() => computeExpensiveValue());
}
}
Serialization:
serialize()-compatible data.Blocking the Event Loop:
sleep() or synchronous HTTP calls in the parent.Thread Limitations:
ext-parallel) are faster but have PHP’s GIL limitations. Prefer processes for CPU-bound tasks.Memory Leaks:
Channel or Worker instances can leak resources. Use finally blocks:
$worker = Worker::createWorker();
try {
$result = $worker->submit(...)->await();
} finally {
$worker->close(); // Critical!
}
Global State:
$_SESSION) is not shared between workers. Use Cache or databases instead.Worker Logs:
stderr to a file in the worker script:
// In child.php
file_put_contents('worker.log', print_r($data, true));
Amp\Log\ConsoleLogger for structured logs.Timeouts:
Execution:
$execution->await(new TimeoutException(5)); // 5-second timeout
Common Exceptions:
WorkerException: Worker process failed.CancelledException: Task was cancelled.SerializationException: Invalid task data.Custom Context Factories:
DefaultContextFactory to customize worker bootstrapping:
class CustomContextFactory extends ProcessContextFactory {
public function start(string $script): Context {
$context = parent::start($script);
$context->send(['env' => 'custom']); // Inject config
return $context;
}
}
Task Middleware:
class LoggingTask implements Task {
public function __construct(private Task $task) {}
public function run(Channel $channel, Cancellation $cancellation) {
\Log::info('Task started', ['task' => get_class($this->task)]);
return $this->task->run($channel, $cancellation);
}
}
Dynamic Worker Pools:
$pool = new WorkerPool(2);
if ($queue->count() > 100) {
$pool->resize(8); // Dynamically add workers
}
Progress Tracking:
Execution::getProgress() for long-running tasks:
$execution = $worker->submit(new LongTask());
while (!$execution->isComplete()) {
$progress = $execution->getProgress();
\Log::info("Progress: {$progress}%");
Amp\delay(1000); // Poll every second
}
How can I help you explore Laravel packages today?