Menu
Reference
- Manticore\Ds
- Async
- Classes
- AggregateError
- CancellationToken
- CancelledException
- Channel
- ChannelClosedException
- Context
- DeadlockException
- Mutex
- Once
- Pool
- Received
- SelectCase
- Selected
- Semaphore
- Settled
- Task
- TaskGroup
- TimeoutException
- Functions
- async()
- awaitAll()
- awaitAllSettled()
- awaitAny()
- channel()
- delay()
- dump()
- dumpOn()
- failure()
- group()
- mapConcurrent()
- mapSettled()
- readFile()
- select()
- selectNow()
- selectWithin()
- shield()
- shutdownOn()
- spawn()
- stats()
- timeout()
- watchdog()
- writeFile()
- Http
- Http\WebSocket
- Buffer
- Process
- Io\Poll
- Manticore\Sapi
- Ffi
Mutex
final class Mutex
A mutual-exclusion lock for a critical section that spans a suspend point.
The scheduler is cooperative and single-threaded: nothing else runs between two suspend points, so an ordinary read-modify-write such as $shared->n = $shared->n + 1 is already atomic. There is no data race to protect against, and no need for atomics. What a Mutex protects is interleaving: a section that suspends in the middle (an await(), a delay(), a stream read) lets other tasks run and observe or change half-updated state.
$mu->withLock(function () use ($store) {
$a = fetch(); // suspends: another task runs here
$b = fetch();
$store->write($a, $b); // must not interleave with another writer
});Waiting tasks are woken in arrival order. The lock is not reentrant: locking it again from the task that holds it throws LogicException instead of deadlocking. It is not tied to the owner on release: unlock() works from any task.
Example
use Async\Mutex;
use function Async\async;
use function Async\delay;
use function Async\spawn;
async(function () {
$mu = new Mutex();
// Each section suspends in the middle. Without the lock the two tasks
// would interleave their steps.
$work = function (string $name) use ($mu) {
$mu->withLock(function () use ($name) {
echo "$name: start\n";
delay(0.01);
echo "$name: end\n";
});
};
$a = spawn(fn() => $work("A"));
$b = spawn(fn() => $work("B"));
$a->await();
$b->await();
// tryLock() never waits.
$mu->lock();
var_dump($mu->tryLock());
$mu->unlock();
var_dump($mu->isLocked());
});Output
A: start
A: end
B: start
B: end
bool(false)
bool(false)Methods
isLocked() | Whether the lock is currently held. |
lock() | Take the lock, suspending until it is free. |
tryLock() | Take the lock only if it is free. |
unlock() | Release the lock and wake the first waiting task. |
withLock() | Hold the lock for the duration of $fn, releasing it however $fn ends. |
isLocked()#source
function isLocked(): bool
Whether the lock is currently held.
Returns
bool — true while some task holds the lock
lock()#source
function lock(): void
Take the lock, suspending until it is free.
Throws
- \LogicException
- when the calling task already holds the lock
- CancelledException
- when the task is cancelled while waiting (the lock is not taken)
See also: withLock, tryLock
tryLock()#source
function tryLock(): bool
Take the lock only if it is free. Never suspends.
Returns
bool — true if the lock was taken; false if someone holds it
Example
use Async\Mutex;
use function Async\async;
use function Async\delay;
use function Async\spawn;
async(function () {
$mu = new Mutex();
// Each section suspends in the middle. Without the lock the two tasks
// would interleave their steps.
$work = function (string $name) use ($mu) {
$mu->withLock(function () use ($name) {
echo "$name: start\n";
delay(0.01);
echo "$name: end\n";
});
};
$a = spawn(fn() => $work("A"));
$b = spawn(fn() => $work("B"));
$a->await();
$b->await();
// tryLock() never waits.
$mu->lock();
var_dump($mu->tryLock());
$mu->unlock();
var_dump($mu->isLocked());
});Output
A: start
A: end
B: start
B: end
bool(false)
bool(false)unlock()#source
function unlock(): void
Release the lock and wake the first waiting task.
Throws
- \LogicException
- when the lock is not held
See also: lock
withLock()#source
function withLock(callable $fn): mixed
Hold the lock for the duration of $fn, releasing it however $fn ends.
Parameters
$fncallable- called with no arguments while the lock is held
Returns
mixed — the value returned by $fn
Throws
- \Throwable
- whatever
$fnthrows, after the lock is released
Example
use Async\Mutex;
use function Async\async;
use function Async\delay;
use function Async\spawn;
async(function () {
$mu = new Mutex();
// Each section suspends in the middle. Without the lock the two tasks
// would interleave their steps.
$work = function (string $name) use ($mu) {
$mu->withLock(function () use ($name) {
echo "$name: start\n";
delay(0.01);
echo "$name: end\n";
});
};
$a = spawn(fn() => $work("A"));
$b = spawn(fn() => $work("B"));
$a->await();
$b->await();
// tryLock() never waits.
$mu->lock();
var_dump($mu->tryLock());
$mu->unlock();
var_dump($mu->isLocked());
});Output
A: start
A: end
B: start
B: end
bool(false)
bool(false)