amphp/sync
Async synchronization primitives for Amp PHP: mutexes, semaphores, locks, and a synchronized helper. Coordinate concurrent fibers, ensure mutual exclusion, and cap parallel work (e.g., limit HTTP requests) with simple, safe abstractions.
Install with composer require amphp/sync. Start with the synchronized() helper and LocalMutex for per-process concurrency control in async code — e.g., protecting a shared in-memory resource like a cache object or counter across fibers. For a first real-world use case, wrap a critical section in your HTTP client that writes to a log or updates a rate-limit counter. The simplest intro is:
use Amp\Sync\LocalMutex;
use function Amp\Sync\synchronized;
$mutex = new LocalMutex();
$counter = 0;
// Safely increment across concurrent coroutines
$increment = function () use (&$counter, $mutex) {
synchronized($mutex, static function () use (&$counter) {
$tmp = $counter;
// Simulate non-atomic read/write
$counter = $tmp + 1;
});
};
Look first at the README’s Mutex and Semaphore sections — they’re the most frequently used primitives and directly replace volatile-style thinking in PHP.
Semaphore with a count to limit concurrency (e.g., 5 concurrent API calls). Wrap async operations like HttpClient::request() in synchronized() or use acquire()/release() for more control. Prefer amphp/pipeline for high-level batch processing — it composes well with Sync\Semaphore for back-pressure.LocalParcel is ideal for sharing simple values (int, string, array) across coroutines without locking mistakes. Wrap read/write logic in parcel->synchronized() callbacks — the mutex used internally ensures safe mutation.SharedMemoryParcel (requires ext-shmop, ext-sysvsem) for fast shared memory on the same host.RedisParcel (with amphp/redis) for distributed locking and coordination.createChannelPair() for full-duplex messaging between parent and child processes (e.g., in amphp/parallel workers).Local* primitives aren’t enough, reach for RedisMutex or SharedMemoryMutex — but ensure your external store (Redis) is resilient and latency-tolerant.synchronized() callbacks: While synchronized() hides lock acquisition/release, blocking I/O inside it defeats the purpose. Always use async functions (Amp\File\write, etc.) inside.synchronized() supports reentry — calling synchronized() again from within the same callback with the same mutex is fine. Still, avoid deep nesting to prevent confusion.Mutex is just a Semaphore with count=1. If you need “only one at a time”, use Mutex. If you need “up to N concurrent” (e.g., connection pool), use Semaphore.Local* primitives (e.g., LocalMutex, LocalSemaphore) only work within a single PHP process. For cross-process or distributed systems, use the *Mutex/*Semaphore variants backed by Redis, shared memory, or files — and test failing dependency paths (e.g., Redis down).createChannelPair($bufferSize) with StreamChannel or ProcessContext to avoid unbounded buffering. Buffer size matters — too large risks OOM, too small causes deadlock under load.FileMutex, ThreadedMutex, and ConcurrentIterator helpers are gone in v2+. Migrate FileMutex to amphp/file and use amphp/pipeline instead of ConcurrentIterator functions.AMPHP_SYNC_DEBUG=1 as an environment variable (if supported by underlying primitives), or log spl_object_id() of locks to trace acquisition/release flow. Enable Redis/IPC error logging if using external stores.How can I help you explore Laravel packages today?