Shared\Counter

OxPHP\Shared\Counter to atomowa 64-bitowa liczba całkowita ze znakiem, działająca w obrębie całego procesu i wyspecjalizowana w akumulacji: zliczaniu zdarzeń, sumowaniu delt, obliczaniu sum w ruchomym oknie. Każda operacja jest nieblokująca; dwa workery dodające współbieżnie nigdy nie gubią ani jednego taktu.

Do dowolnego stanu atomowego, który musi synchronizować inną pamięć (automaty skończone, znaczniki wersji, seqlocki, maski bitowych flag), użyj zamiast tego Shared\Atomic.

Przegląd

  • Atomowy int64. Zakres −9_223_372_036_854_775_808 … 9_223_372_036_854_775_807. Przepełnienie zawija się.
  • Nieblokujący. add kompiluje się do pojedynczego fetch_add.
  • Zawsze Relaxed. Operacje są atomowe (żadnych zgubionych taktów, żadnych rozdartych odczytów), ale nie ustanawiają żadnej relacji happens-before z inną pamięcią. Counter to statystyka, a nie punkt synchronizacji — jeśli potrzebujesz uporządkowania, użyj Shared\Atomic.
  • Współdzielony. Instancje można przechowywać wewnątrz Shared\Map / Shared\Channel i przekazywać do Fiberów przez przechwycenia use.

Dokumentacja API

php
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; }
Metoda Zwraca Zastosowanie
get bieżąca Odczyt bez modyfikacji.
set poprzednia Atomowa wymiana; set(0) to odczyt-i-wyzerowanie na koniec okna.
add nowa add() zwiększa o 1, add(-1) zmniejsza, w innym wypadku dowolna delta.
compareAndSet bool Liczniki z ograniczeniem / nasyceniem (limit, dolna granica) przez pętlę CAS.
id id w rejestrze Logowanie, śledzenie, korelacja /__ox_shared/entry?id=….

Przykłady

Licznik żądań na worker

php
<?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"; });

Przełączanie okien

php
<?php $hits = new OxPHP\Shared\Counter(); // Every N minutes in your cron/worker loop: $prev = $hits->set(0); // atomically reads and zeroes logWindowMetric($prev);

Licznik z ograniczeniem (pętla CAS)

php
<?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));

Akumulacja zbiorcza

php
<?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));

Semantyka i pułapki

set() zwraca poprzednią wartość, a następnie zapisuje — atomowo. set(0) to wzorzec migawka-i-zerowanie (LongAdder::sumThenReset); set($n) ustawia dowolny nowy punkt startowy.

Uporządkowanie Relaxed

Każda operacja jest atomowa, ale Counter nie publikuje innej pamięci. Jeśli czytelnik musi zobaczyć dane, które piszący zapisał przed zwiększeniem liczby całkowitej, to jest synchronizacja — użyj Shared\Atomic z Ordering::Release/Acquire.

compareAndSet działa w trybie Relaxed/Relaxed i nie przyjmuje argumentów uporządkowania. Jest poprawny dla decyzji podejmowanych na podstawie własnej wartości licznika (limit, dolna granica, przejęcie na podstawie wartości). CAS, który publikuje inny stan, należy do Shared\Atomic.

Przepełnienie zawija się

Dodawanie powyżej INT_MAX zawija się do INT_MIN. Dla liczników działających miesiącami z prędkością tysięcy operacji na sekundę utrzymuj wartość w zakresie dziesiątek bilionów lub resetuj ją okresowo.

Brak wartości ułamkowych. Liczysz bajty na potrzeby średnich o precyzji zmiennoprzecinkowej? Śledź licznik (Counter) i mianownik (Counter) osobno i dziel je w momencie odczytu.

Wyjątki

Wyjątek Zgłaszany przez
StaleHandleException Dowolną metodę na uchwycie, którego wpis w rejestrze usunięto.
UninitializedException id() na wrapperze, który nie zakończył __construct.

Liczniki nigdy nie zgłaszają wyjątku przy przepełnieniu ani wartościach skrajnych — zawijają się.

Obserwowalność

Pełny przegląd znajdziesz w Obserwowalności stanu współdzielonego. Szybkie odniesienia:

  • GET /__ox_shared/entry?id=N udostępnia { value, type: "Counter" }.
  • Miernik Prometheusa oxphp_shared_counter_value{counter_id="…"} śledzi bieżącą wartość.
  • Liczniki obejmujące cały rejestr (oxphp_shared_operations_total, oxphp_shared_objects_total) uwzględniają Counter dzięki etykiecie type="Counter".

Kiedy nie używać

  • Liczby zmiennoprzecinkowe lub dziesiętne. Użyj pary liczników Counter (licznik / mianownik) albo Shared\Mutex<array{total_cents: int, count: int}>.
  • Zdarzenia nienumeryczne wymagające bogatego kontekstu. Jeśli potrzebujesz {count, last_actor, last_reason} powiązanego z jednym kluczem, sięgnij po Shared\Map lub Shared\Mutex.
  • Sumy między hostami. Counter działa wyłącznie w obrębie procesu. Do agregacji między wieloma hostami użyj potoku metryk (Prometheus + rate() albo centralne INCR w Redisie).
  • Trwałość. Stan licznika Counter znika przy zatrzymaniu serwera. Zapisuj migawki gdzie indziej, jeśli suma musi przetrwać restarty.

Powiązane

  • Stan współdzielony — przegląd i wzorce migracji.
  • Shared\Atomic — uniwersalny atomowy int64 z CAS, swapem i pełną kontrolą uporządkowania pamięci.
  • Shared\Map — gdy liczby są kluczowane (Map<string, Counter>).
  • Shared\Flag — gdy wartość to po prostu włączony/wyłączony.
  • Shared\Mutex — gdy licznik musi być aktualizowany w zgraniu z innymi polami.