Shared\Counter
OxPHP\Shared\Counter — это атомарное 64-битное знаковое целое, действующее в пределах всего процесса и специализированное на накоплении: подсчёте событий, суммировании дельт, скользящих оконных итогах. Все операции неблокирующие; два воркера, выполняющих сложение конкурентно, никогда не теряют ни одного тика.
Для произвольного атомарного состояния, которое должно синхронизировать другую память (конечные автоматы, метки версий, seqlock'и, битовые маски флагов), используйте Shared\Atomic.
Обзор
- Атомарный int64. Диапазон
−9_223_372_036_854_775_808 … 9_223_372_036_854_775_807. Переполнение заворачивается по кругу. - Неблокирующий (lock-free).
addкомпилируется в единственныйfetch_add. - Всегда Relaxed. Операции атомарны (без потерянных тиков, без разорванных чтений), но не устанавливают отношения happens-before с другой памятью. Counter — это статистика, а не точка синхронизации; если вам нужна упорядоченность, используйте
Shared\Atomic. - Разделяемый. Экземпляры можно хранить внутри
Shared\Map/Shared\Channelи передавать в файберы через захватыuse.
Справочник по API
namespace OxPHP\Shared;
final class Counter implements Shareable
{
public function __construct(int $initial = 0);
public function get(): int; // current
public function set(int $value): int; // returns previous; set(0) = window reset
public function add(int $delta = 1): int; // returns new; add()=+1, add(-1)=decrement
public function compareAndSet(int $expect, int $new): bool;
public function id(): int;
}| Метод | Возвращает | Применение |
|---|---|---|
get |
текущее | Чтение без изменения. |
set |
предыдущее | Атомарный обмен; set(0) — чтение с обнулением в конце окна. |
add |
новое | add() увеличивает на 1, add(-1) уменьшает, иначе — на произвольную дельту. |
compareAndSet |
bool | Ограниченные / насыщающиеся счётчики (потолок, пол) через цикл CAS. |
id |
id в реестре | Логирование, трассировка, корреляция через /__ox_shared/entry?id=…. |
Примеры
Счётчик запросов на воркер
<?php
$requests = new OxPHP\Shared\Counter();
oxphp_worker(function () use ($requests) {
$count = $requests->add(); // +1, returns the new total
header("X-Request-Count: {$count}");
echo "ok";
});Оконный сброс (rollover)
<?php
$hits = new OxPHP\Shared\Counter();
// Every N minutes in your cron/worker loop:
$prev = $hits->set(0); // atomically reads and zeroes
logWindowMetric($prev);Ограниченный счётчик (цикл CAS)
<?php
$slots = new OxPHP\Shared\Counter();
$cap = 100;
// Claim a slot only while under the cap.
do {
$cur = $slots->get();
if ($cur >= $cap) {
// full — reject
break;
}
} while (!$slots->compareAndSet($cur, $cur + 1));Пакетное накопление
<?php
$bytes = new OxPHP\Shared\Counter();
// Sum a batch in PHP, then one atomic add (one FFI call).
$deltas = array_map(fn ($req) => strlen($req['body']), $batch);
$newTotal = $bytes->add(array_sum($deltas));Семантика и подводные камни
set() возвращает предыдущее значение, а затем сохраняет новое — атомарно. set(0) — это паттерн снимок-с-обнулением (LongAdder::sumThenReset); set($n) задаёт любую новую стартовую точку.
Каждая операция атомарна, но Counter не публикует другую память. Если читатель должен увидеть данные, которые писатель записал до инкремента целого, — это синхронизация, используйте Shared\Atomic с Ordering::Release/Acquire.
compareAndSet работает в режиме Relaxed/Relaxed и не принимает аргументов упорядочения. Он корректен для решений, принимаемых по собственному значению счётчика (потолок, пол, захват по значению). CAS, публикующий другое состояние, — это задача для Shared\Atomic.
Сложение за пределами INT_MAX заворачивается к INT_MIN. Для счётчиков, работающих месяцами со скоростью в тысячи операций в секунду, держите значение в диапазоне десятков триллионов или периодически сбрасывайте его.
Никаких дробных значений. Считаете байты для усреднений с точностью float? Отслеживайте числитель (Counter) и знаменатель (Counter) по отдельности и делите при чтении.
Исключения
| Исключение | Кем возбуждается |
|---|---|
StaleHandleException |
Любой метод дескриптора, запись которого была вытеснена из реестра. |
UninitializedException |
id() на обёртке, ещё не завершившей __construct. |
Счётчики никогда не выбрасывают исключения при переполнении или экстремальных значениях — они заворачиваются по кругу.
Наблюдаемость
Полный обзор см. в Наблюдаемость разделяемого состояния. Краткая справка:
GET /__ox_shared/entry?id=Nвозвращает{ value, type: "Counter" }.- Prometheus-датчик (gauge)
oxphp_shared_counter_value{counter_id="…"}отслеживает текущее значение. - Счётчики уровня всего реестра (
oxphp_shared_operations_total,oxphp_shared_objects_total) покрывают Counter через меткуtype="Counter".
Когда не стоит использовать
- Числа с плавающей точкой или десятичные. Используйте пару Counter'ов (числитель / знаменатель) или
Shared\Mutex<array{total_cents: int, count: int}>. - Нечисловые события, требующие богатого контекста. Если вам нужно связать
{count, last_actor, last_reason}с одним ключом, обратитесь кShared\MapилиShared\Mutex. - Итоги между хостами. Counter существует только в пределах процесса. Для агрегации по нескольким хостам используйте конвейер метрик (Prometheus +
rate()или центральныйINCRв Redis). - Долговечность. Состояние Counter исчезает при остановке сервера. Сохраняйте снимки в другом месте, если итог должен пережить перезапуски.
Связанные материалы
- Разделяемое состояние — обзор и паттерны миграции.
- Shared\Atomic — универсальный атомарный int64 с CAS, swap и полным контролем упорядочения памяти.
- Shared\Map — когда счётчики привязаны к ключам (
Map<string, Counter>). - Shared\Flag — когда значение — это просто вкл/выкл.
- Shared\Mutex — когда счётчик должен обновляться синхронно с другими полями.