Shared\Atomic

OxPHP\Shared\Atomic to atomowa 64-bitowa liczba całkowita ze znakiem, działająca w obrębie całego procesu, oferująca pełen zestaw operacji prymitywnych: load, store, swap, compareAndSet oraz fetchAdd/Sub/And/Or/Xor. Każda operacja jest lock-free, a uporządkowanie pamięci jest jawne i domyślnie wynosi SeqCst.

Przegląd

  • Prymityw atomowy int64. Zakres −9_223_372_036_854_775_808 … 9_223_372_036_854_775_807. Przepełnienie zawija się.
  • Lock-free. Każda operacja kompiluje się do pojedynczej atomowej instrukcji CPU (load, store, xchg, cmpxchg, xadd itd.).
  • Uporządkowanie pamięci wybierasz sam. Przekaż wartość enuma OxPHP\Shared\Ordering, gdy potrzebujesz Relaxed / Acquire / Release / AcqRel / SeqCst. Domyślnie jest to SeqCst, więc wywołujący, którym to obojętne, otrzymują najsilniejszą gwarancję.

Kiedy używać Atomic zamiast Shared\Counter:

  • Automaty stanówcompareAndSet dla idle → busy → done.
  • Znaczniki wersji / liczniki generacjifetchAdd(1) zwraca poprzednią wersję; odczytujący mogą jej użyć do wykrywania wyścigów.
  • Pętle CAS — odczyt przez load, obliczenie nowej wartości, ponawianie compareAndSet aż do powodzenia.
  • Maski flag bitowychfetchOr do ustawienia, fetchAnd do wyczyszczenia.

Counter to właściwe narzędzie do akumulacji (add); Atomic to właściwe narzędzie do dowolnego atomowego stanu.

Dokumentacja API

php
namespace OxPHP\Shared; final class Atomic implements Shareable { public function __construct(int $initial = 0); public function load(Ordering $order = Ordering::SeqCst): int; public function store(int $value, Ordering $order = Ordering::SeqCst): void; public function swap(int $value, Ordering $order = Ordering::SeqCst): int; // returns prev public function compareAndSet( int $expect, int $new, Ordering $success = Ordering::SeqCst, Ordering $failure = Ordering::SeqCst, ): bool; public function fetchAdd(int $delta, Ordering $order = Ordering::SeqCst): int; // returns prev public function fetchSub(int $delta, Ordering $order = Ordering::SeqCst): int; // returns prev public function fetchAnd(int $mask, Ordering $order = Ordering::SeqCst): int; // returns prev public function fetchOr (int $mask, Ordering $order = Ordering::SeqCst): int; // returns prev public function fetchXor(int $mask, Ordering $order = Ordering::SeqCst): int; // returns prev public function id(): int; }
Metoda Zwraca Zastosowanie
load bieżąca Odczyt wartości z wybranym uporządkowaniem.
store void Zapis nowej wartości z odrzuceniem starej.
swap poprzednia Atomowe zastąpienie; swap(0) to wzorzec pobrania migawki i wyzerowania.
compareAndSet zamieniono? Optymistyczne przejścia i pętle CAS.
fetchAdd/Sub poprzednia Liczniki generacji, ograniczone liczniki przez CAS, delty.
fetchAnd/Or/Xor poprzednia Maski flag bitowych: ustawianie, czyszczenie, przełączanie.
id id z rejestru Logowanie, śledzenie, korelacja /__ox_shared/entry?id=….

Uporządkowanie pamięci

Krótkie wprowadzenie:

  • Relaxed — wyłącznie atomowość, brak uporządkowania względem innych dostępów do pamięci.
  • Acquire (odczyty) — łączy się w parę ze store'em Release; odczyty po tej operacji widzą zapisy, które zwalniający zdążył ukończyć.
  • Release (zapisy) — łączy się w parę z odczytem Acquire; zapisy sprzed tej operacji są widoczne dla przejmujących.
  • AcqRel (read-modify-write) — obie połowy: odczyt Acquire i zapis Release.
  • SeqCst — jeden globalny, pełny porządek wszystkich operacji SeqCst.

Każda operacja akceptuje tylko te uporządkowania, które mają dla niej sens:

Operacja Dozwolone
load Relaxed, Acquire, SeqCst
store Relaxed, Release, SeqCst
swap, fetchAdd, fetchSub, fetchAnd, fetchOr, fetchXor dowolne
compareAndSet success dowolne
compareAndSet failure Relaxed, Acquire, SeqCst

Domyślnie wszędzie jest Ordering::SeqCst, więc wywołujący, którzy nie zastanawiają się nad uporządkowaniem, i tak otrzymują bezpieczne zachowanie. Nieprawidłowa kombinacja rzuca OxPHP\Shared\InvalidOrderingException przed wywołaniem FFI.

Aby zgłębić model pamięci C++/Rust, zobacz dokumentację std::sync::atomic::Ordering w Ruście.

Przykłady

Automat stanów przez compareAndSet

php
<?php use OxPHP\Shared\Atomic; $state = new Atomic(initial: 0); // 0=idle, 1=busy, 2=done if (!$state->compareAndSet(expect: 0, new: 1)) { throw new RuntimeException('another worker is already processing'); } try { doWork(); $state->store(2); } catch (Throwable $e) { $state->store(0); // release back to idle on error throw $e; }

Licznik generacji / znacznik wersji

php
<?php $version = new OxPHP\Shared\Atomic(); // Each writer bumps the version and gets the value it just superseded. $prev = $version->fetchAdd(1); publishUpdate($prev + 1, $payload);

Optymistyczna aktualizacja przez pętlę CAS

php
<?php use OxPHP\Shared\Atomic; use OxPHP\Shared\Ordering; $cell = new Atomic(initial: 100); // Saturate-add: never go above 1000. do { $cur = $cell->load(Ordering::Acquire); $next = min($cur + 7, 1000); if ($cur === $next) { break; // already at cap } } while (!$cell->compareAndSet($cur, $next, Ordering::AcqRel, Ordering::Acquire));

Maska flag bitowych

php
<?php const FLAG_READY = 1 << 0; const FLAG_DRAINING = 1 << 1; const FLAG_FAILED = 1 << 2; $flags = new OxPHP\Shared\Atomic(); $flags->fetchOr(FLAG_READY); // set bit $flags->fetchAnd(~FLAG_DRAINING); // clear bit $snapshot = $flags->load(); if ($snapshot & FLAG_FAILED) { raiseAlert(); }

Semantyka i pułapki

fetchAdd zwraca poprzednią wartość

fetchAdd zwraca poprzednią wartość, a nie nową. Jest to celowe przeciwieństwo Counter::add, które zwraca nową sumę. Inna abstrakcja, inna konwencja zwracania: wybierz klasę pasującą do semantyki, o którą ci chodzi.

Przepełnienie zawija się

i64::MIN.fetchSub(1) daje i64::MAX. Nie jest rzucany żaden wyjątek.

Domyślne uporządkowanie to SeqCst

SeqCst to najbezpieczniejszy i zarazem najwolniejszy wybór. Zejdź do Acquire/Release/Relaxed tylko wtedy, gdy potrafisz uzasadnić dlaczego.

Atomic przechowuje pojedynczą liczbę int64. Dla stanu złożonego (wielu powiązanych pól) użyj Shared\Mutex.

Wyjątki

Wyjątek Rzucany przez
StaleHandleException Dowolna metoda na uchwycie, którego wpis w rejestrze został usunięty.
UninitializedException id() na obiekcie opakowującym, który nie ukończył __construct.
InvalidOrderingException Operacja otrzymuje uporządkowanie pamięci dla niej nieprawidłowe.

Obserwowalność

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

  • GET /__ox_shared/entry?id=N udostępnia { value, type: "Atomic" }.
  • Liczniki na poziomie całego rejestru (oxphp_shared_operations_total, oxphp_shared_objects_total) uwzględniają Atomic dzięki etykiecie type="Atomic".

Kiedy nie używać

  • Stan złożony. Wiele pól, które muszą się aktualizować razem → Shared\Mutex.
  • Liczenie / akumulacja. Użyj Shared\Counter — jego add zwracające nową sumę pasuje do dziedziny.
  • Liczby zmiennoprzecinkowe lub dziesiętne. Nieobsługiwane; opakuj strukturę w Shared\Mutex albo zestaw w parę dwa obiekty Counter (licznik / mianownik).
  • Koordynacja między hostami. Atomic działa wyłącznie w obrębie procesu. Dla stanu obejmującego wiele hostów użyj Redisa, bazy danych lub potoku metryk.
  • Trwałość. Stan Atomic ulatnia się przy zatrzymaniu serwera. Zapisuj migawki w innym miejscu, jeśli wartość musi przetrwać restarty.

Powiązane