Menu

Reference / Async

Semaphore

finalclass
final class Semaphore

A counting semaphore: the bound on "do N of these at a time".

acquire() takes a permit and parks the task, not the process, while none is free, so a limiter costs nothing while it waits. Waiting tasks are woken in arrival order as permits are released. withPermit() is the safe form that releases the permit however the callable ends. For the common "map over a list, N at a time" shape see mapConcurrent().

$sem = new Async\Semaphore(10);
$sem->withPermit(fn() => fetch($url));

Example

use Async\Semaphore;
use function Async\async;
use function Async\awaitAll;
use function Async\delay;
use function Async\spawn;

async(function () {
    // At most two of the five tasks hold a permit at any moment.
    $sem = new Semaphore(2);
    $active = 0;
    $peak = 0;

    $tasks = [];
    for ($i = 1; $i <= 5; $i++) {
        $tasks[] = spawn(function () use ($sem, &$active, &$peak, $i) {
            $sem->withPermit(function () use (&$active, &$peak) {
                $active++;
                $peak = max($peak, $active);
                delay(0.01);
                $active--;
            });
            return $i;
        });
    }

    echo implode(",", awaitAll(...$tasks)), "\n";
    echo "peak: $peak, free: ", $sem->available(), "\n";
});

Output

1,2,3,4,5
peak: 2, free: 2

See also: mapConcurrent, Mutex

Methods

__construct()Create a semaphore holding $permits permits.
available()Permits currently available.
acquire()Take a permit, suspending until one is free.
release()Return a permit and wake the first waiting task.
withPermit()Hold a permit for the duration of $fn, releasing it however $fn ends.

__construct()#source

function __construct(int $permits)

Create a semaphore holding $permits permits.

Parameters

$permits int
number of permits; values below 1 are raised to 1

available()#source

function available(): int

Permits currently available.

Returns

int โ€” the number of permits that can be acquired without waiting

acquire()#source

function acquire(): void

Take a permit, suspending until one is free.

Every acquire() must be paired with a release(); prefer withPermit(), which cannot forget.

Throws

CancelledException
when the task is cancelled while waiting (no permit is taken)

See also: release, withPermit

release()#source

function release(): void

Return a permit and wake the first waiting task.

Throws

\LogicException
when every permit is already free: a release without a matching acquire is a caller bug and would lift the bound

See also: acquire

withPermit()#source

function withPermit(callable $fn): mixed

Hold a permit for the duration of $fn, releasing it however $fn ends.

Parameters

$fn callable
called with no arguments while the permit is held

Returns

mixed โ€” the value returned by $fn

Throws

\Throwable
whatever $fn throws, after the permit is released

Example

use Async\Semaphore;
use function Async\async;
use function Async\awaitAll;
use function Async\delay;
use function Async\spawn;

async(function () {
    // At most two of the five tasks hold a permit at any moment.
    $sem = new Semaphore(2);
    $active = 0;
    $peak = 0;

    $tasks = [];
    for ($i = 1; $i <= 5; $i++) {
        $tasks[] = spawn(function () use ($sem, &$active, &$peak, $i) {
            $sem->withPermit(function () use (&$active, &$peak) {
                $active++;
                $peak = max($peak, $active);
                delay(0.01);
                $active--;
            });
            return $i;
        });
    }

    echo implode(",", awaitAll(...$tasks)), "\n";
    echo "peak: $peak, free: ", $sem->available(), "\n";
});

Output

1,2,3,4,5
peak: 2, free: 2

View source