Shared\Atomic
OxPHP\Shared\Atomic — це атомарне 64-бітне ціле зі знаком, спільне для всього процесу, з повним набором примітивів: load, store, swap, compareAndSet, а також fetchAdd/Sub/And/Or/Xor. Кожна операція безблокувальна, а порядок доступу до пам’яті задається явно і за замовчуванням дорівнює SeqCst.
Огляд
- Атомарний примітив int64. Діапазон
−9_223_372_036_854_775_808 … 9_223_372_036_854_775_807. Переповнення відбувається з циклічним переходом. - Без блокувань. Кожна операція компілюється в одну атомарну інструкцію процесора (
load,store,xchg,cmpxchg,xaddтощо). - Порядок доступу до пам’яті обираєте ви. Передайте значення переліку
OxPHP\Shared\Ordering, коли вам потрібенRelaxed/Acquire/Release/AcqRel/SeqCst. За замовчуванням використовуєтьсяSeqCst, тож той, кому це байдуже, отримує найсильнішу гарантію.
Коли варто обрати Atomic замість Shared\Counter:
- Скінченні автомати —
compareAndSetдля переходівidle → busy → done. - Мітки версій / лічильники поколінь —
fetchAdd(1)повертає попередню версію; читачі можуть використати її, щоб виявити гонки. - Цикли CAS — прочитайте значення через
load, обчисліть нове й повторюйтеcompareAndSet, доки операція не вдасться. - Маски бітових прапорців —
fetchOr, щоб встановити біт,fetchAnd, щоб скинути.
Counter — правильний інструмент для накопичення (add); Atomic — правильний інструмент для довільного атомарного стану.
Довідник 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;
}| Метод | Повертає | Сценарій використання |
|---|---|---|
load |
поточне | Прочитати значення з обраним порядком. |
store |
void | Записати нове значення, відкинувши старе. |
swap |
попереднє | Атомарна заміна; swap(0) — патерн «знімок і обнулення». |
compareAndSet |
чи замінено | Оптимістичні переходи та цикли CAS. |
fetchAdd/Sub |
попереднє | Лічильники поколінь, обмежені лічильники через CAS, дельти. |
fetchAnd/Or/Xor |
попереднє | Маски бітових прапорців: встановити, скинути, інвертувати. |
id |
id у реєстрі | Журналювання, трасування, кореляція з /__ox_shared/entry?id=…. |
Порядок доступу до пам’яті
Коротка довідка:
- Relaxed — лише атомарність, без упорядкування відносно інших звернень до пам’яті.
- Acquire (читання) — утворює пару із записом
Release; читання після цієї операції бачать записи, які той, хто виконав release, уже завершив. - Release (записи) — утворює пару з читанням
Acquire; записи до цієї операції стають видимими для тих, хто виконує acquire. - AcqRel (читання-модифікація-запис) — обидві половини: читання Acquire і запис Release.
- SeqCst — єдиний глобальний повний порядок для всіх операцій
SeqCst.
Кожна операція приймає лише ті порядки, які для неї мають сенс:
| Операція | Дозволено |
|---|---|
load |
Relaxed, Acquire, SeqCst |
store |
Relaxed, Release, SeqCst |
swap, fetchAdd, fetchSub, fetchAnd, fetchOr, fetchXor |
будь-який |
compareAndSet success |
будь-який |
compareAndSet failure |
Relaxed, Acquire, SeqCst |
За замовчуванням усюди використовується Ordering::SeqCst, тож навіть ті, хто не замислюється про порядок, отримують безпечну поведінку. Неприпустима комбінація викидає OxPHP\Shared\InvalidOrderingException ще до виклику FFI.
Докладніше про модель пам’яті C++/Rust — у документації Rust std::sync::atomic::Ordering.
Приклади
Скінченний автомат через 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;
}Лічильник поколінь / мітка версії
<?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);Оптимістичне оновлення через цикл 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));Маска бітових прапорців
<?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();
}Семантика та підводні камені
fetchAdd повертає попереднє значення, а не нове. Це свідомий контраст із Counter::add, який повертає новий підсумок. Інша абстракція — інша домовленість щодо значення, що повертається: обирайте клас, семантика якого відповідає вашому задуму.
i64::MIN.fetchSub(1) дає i64::MAX. Жодного винятку не викидається.
SeqCst — найбезпечніший вибір і найповільніший. Переходьте на Acquire/Release/Relaxed лише тоді, коли можете чітко пояснити навіщо.
Atomic зберігає одне значення int64. Для складеного стану (кількох пов’язаних полів) використовуйте Shared\Mutex.
Винятки
| Виняток | Хто викидає |
|---|---|
StaleHandleException |
Будь-який метод дескриптора, чий запис у реєстрі було витіснено. |
UninitializedException |
id() на обгортці, яка ще не завершила __construct. |
InvalidOrderingException |
Операція отримує неприпустимий для неї порядок доступу до пам’яті. |
Спостережуваність
Повний огляд — у розділі Спостережуваність спільного стану. Коротко:
GET /__ox_shared/entry?id=Nповертає{ value, type: "Atomic" }.- Лічильники всього реєстру (
oxphp_shared_operations_total,oxphp_shared_objects_total) охоплюють Atomic через міткуtype="Atomic".
Коли не варто використовувати
- Складений стан. Кілька полів, що мають оновлюватися разом →
Shared\Mutex. - Підрахунок / накопичення. Використовуйте
Shared\Counter— йогоadd, що повертає новий підсумок, відповідає предметній області. - Числа з рухомою комою або десяткові дроби. Не підтримуються; загорніть структуру в
Shared\Mutexабо поєднайте два Counter (чисельник / знаменник). - Координація між хостами. Atomic працює лише в межах процесу. Для стану на кількох хостах використовуйте Redis, базу даних або конвеєр метрик.
- Довговічність. Стан Atomic зникає після зупинки сервера. Якщо значення має пережити перезапуск, зберігайте знімки деінде.
Промпти для вашого агента
Ці промпти розраховані на агента з підключеним MCP-сервером документації OxPHP — потрібні сторінки він прочитає сам.
Використай OxPHP Shared Atomic у моєму коді замість прапорця стану або бітової маски, які я оновлюю звичайним «прочитати, потім записати». Прочитай сторінку `shared-state/shared-atomic` через MCP-сервер документації OxPHP.
Знайди один такий випадок у цьому репозиторії й покажи мені заміну на основі `compareAndSet`, зокрема який порядок доступу до пам’яті їй насправді потрібен.Пов’язані матеріали
- Спільний стан — огляд і патерни міграції.
- Shared\Counter — коли значення є накопичувачем предметної області.
- Shared\Mutex — коли стан займає більше ніж одне int64.
- Shared\Flag — коли значення — просто увімкнено/вимкнено.