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

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; }
Метод Повертає Сценарій використання
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
<?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
<?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
<?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
<?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 повертає попереднє значення

fetchAdd повертає попереднє значення, а не нове. Це свідомий контраст із Counter::add, який повертає новий підсумок. Інша абстракція — інша домовленість щодо значення, що повертається: обирайте клас, семантика якого відповідає вашому задуму.

Переповнення циклічне

i64::MIN.fetchSub(1) дає i64::MAX. Жодного винятку не викидається.

Порядок за замовчуванням — SeqCst

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 — потрібні сторінки він прочитає сам.

prompt
Використай OxPHP Shared Atomic у моєму коді замість прапорця стану або бітової маски, які я оновлюю звичайним «прочитати, потім записати». Прочитай сторінку `shared-state/shared-atomic` через MCP-сервер документації OxPHP. Знайди один такий випадок у цьому репозиторії й покажи мені заміну на основі `compareAndSet`, зокрема який порядок доступу до пам’яті їй насправді потрібен.

Пов’язані матеріали

  • Спільний стан — огляд і патерни міграції.
  • Shared\Counter — коли значення є накопичувачем предметної області.
  • Shared\Mutex — коли стан займає більше ніж одне int64.
  • Shared\Flag — коли значення — просто увімкнено/вимкнено.