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,xadditd.). - Uporządkowanie pamięci wybierasz sam. Przekaż wartość enuma
OxPHP\Shared\Ordering, gdy potrzebujeszRelaxed/Acquire/Release/AcqRel/SeqCst. Domyślnie jest toSeqCst, więc wywołujący, którym to obojętne, otrzymują najsilniejszą gwarancję.
Kiedy używać Atomic zamiast Shared\Counter:
- Automaty stanów —
compareAndSetdlaidle → busy → done. - Znaczniki wersji / liczniki generacji —
fetchAdd(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, ponawianiecompareAndSetaż do powodzenia. - Maski flag bitowych —
fetchOrdo ustawienia,fetchAnddo wyczyszczenia.
Counter to właściwe narzędzie do akumulacji (add); Atomic to właściwe narzędzie do dowolnego atomowego stanu.
Dokumentacja API
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
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
$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
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
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ść, 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.
i64::MIN.fetchSub(1) daje i64::MAX. Nie jest rzucany żaden wyjątek.
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=Nudostępnia{ value, type: "Atomic" }.- Liczniki na poziomie całego rejestru (
oxphp_shared_operations_total,oxphp_shared_objects_total) uwzględniają Atomic dzięki etykiecietype="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— jegoaddzwracające nową sumę pasuje do dziedziny. - Liczby zmiennoprzecinkowe lub dziesiętne. Nieobsługiwane; opakuj strukturę w
Shared\Mutexalbo 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
- Stan współdzielony — przegląd i wzorce migracji.
- Shared\Counter — gdy wartość jest akumulatorem dziedzinowym.
- Shared\Mutex — gdy stan obejmuje więcej niż jedną liczbę int64.
- Shared\Flag — gdy wartość to po prostu włączone/wyłączone.