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。溢出会回绕。 - 无锁。 每个操作都编译为单条 CPU 原子指令(
load、store、xchg、cmpxchg、xadd等)。 - 内存序由你决定。 当你需要
Relaxed/Acquire/Release/AcqRel/SeqCst时,传入一个OxPHP\Shared\Ordering枚举值。默认值是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(用于 load)—— 与
Release存储配对;该操作之后的读取能观察到 releaser 已经完成的写入。 - Release(用于 store)—— 与
Acquire加载配对;该操作之前的写入对 acquirer 可见。 - 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,因此不去考虑内存序的调用方依然能获得安全的行为。无效的组合会在 FFI 调用之前抛出 OxPHP\Shared\InvalidOrderingException。
想深入了解 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 |
在尚未完成 __construct 的包装器上调用 id()。 |
InvalidOrderingException |
某个操作收到了对它无效的内存序。 |
可观测性
完整介绍请参阅 Shared 可观测性。快速参考:
GET /__ox_shared/entry?id=N暴露{ value, type: "Atomic" }。- 注册表级别的计数器(
oxphp_shared_operations_total、oxphp_shared_objects_total)通过type="Atomic"标签覆盖 Atomic。
何时不该使用
- 复合状态。 多个必须一起更新的字段 →
Shared\Mutex。 - 计数 / 累加。 使用
Shared\Counter—— 它的add返回新的总和,更贴合该领域。 - 浮点数或小数。 不支持;把一个结构体包进
Shared\Mutex,或者配对使用两个 Counter(分子 / 分母)。 - 跨主机协调。 Atomic 仅在进程内有效。多主机状态请使用 Redis、数据库或指标管道。
- 持久性。 Atomic 状态在服务器停止时会消失。如果值必须在重启后存活,请把快照持久化到别处。
相关内容
- 共享状态 —— 概述与迁移模式。
- Shared\Counter —— 当值是一个领域累加器时。
- Shared\Mutex —— 当状态跨越多个 int64 时。
- Shared\Flag —— 当值只是开/关时。