Mutex vs Semaphore
debt(d9/e5/b5/t7)
Closest to 'silent in production until users hit it' (d9). The detection_hints field explicitly states automated=no and the code_pattern is a check-then-modify on shared resource without locking. There is no static analysis tool listed that can catch this; race conditions only manifest under concurrent load and typically surface as data corruption, duplicate records, or double charges in production — not in local or single-process testing.
Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix points to Redis SETNX as the solution, but adopting distributed locking requires identifying all shared-resource access points, wrapping them with acquire/release logic using try/finally, setting appropriate TTLs, and potentially replacing existing flock-based approaches. This is more than a single-line swap but unlikely to be a full cross-cutting architectural rework unless the pattern is pervasive.
Closest to 'persistent productivity tax' (b5). The applies_to scope covers web, cli, and queue-worker contexts — essentially all PHP execution models. Once distributed locking is introduced, every future developer touching shared-resource operations must understand the locking strategy, TTL configuration, and exception-safe release patterns. This imposes ongoing cognitive overhead across multiple work streams without being fully architectural.
Closest to 'serious trap (contradicts how a similar concept works elsewhere)' (t7). The misconception field explicitly states that developers believe PHP is single-threaded so mutexes don't apply — a plausible and common assumption for those familiar with single-process PHP scripts. PHP-FPM's multi-process model contradicts this mental model entirely. Combined with the common mistake of using flock for distributed coordination (which works in other single-server languages/contexts), this is a serious trap that catches competent developers who reason from correct-but-incomplete premises.
Also Known As
TL;DR
Explanation
A mutex (mutual exclusion) is a binary lock — one owner at a time, the same thread must unlock it. A semaphore is a counter — initialised to N, decremented on acquire, incremented on release; N threads can access the resource concurrently. Binary semaphore (N=1) differs from mutex because any thread can release it. In PHP, true thread-level mutexes require pthreads or Swoole. For web applications, Redis-based distributed locks (SETNX with expiry) serve as mutex equivalents across processes.
Common Misconception
Why It Matters
Common Mistakes
- Using PHP file locks (flock) for distributed coordination — only works on a single server.
- Forgetting to release the lock on exception — use try/finally to guarantee release.
- Setting lock TTL too short — the operation completes after the lock expires, allowing a second process to acquire it mid-operation.
Avoid When
- Do not use file-based locks (flock) in multi-server deployments — they only work on a single machine.
- Avoid long lock hold times — keep the critical section as short as possible to reduce contention.
When To Use
- Use a distributed mutex when multiple PHP workers could race to process the same resource — orders, payments, job dispatching.
- Use a semaphore when you need to limit concurrency to N workers — e.g. at most 3 concurrent exports.
Code Examples
// No locking — two workers process the same order simultaneously
if (getOrderStatus($orderId) === 'pending') {
// Another worker may pass this check at the same moment
processOrder($orderId); // double processing
}
// Redis distributed mutex via Predis
$lockKey = 'lock:order:' . $orderId;
$lockValue = bin2hex(random_bytes(16)); // unique per acquisition
$acquired = $redis->set($lockKey, $lockValue, 'NX', 'EX', 10); // 10s TTL
if (!$acquired) {
throw new \RuntimeException('Could not acquire lock');
}
try {
processOrder($orderId);
} finally {
// Only release if we own the lock
if ($redis->get($lockKey) === $lockValue) {
$redis->del($lockKey);
}
}