OxPHP\Shared\* 命名约定

OxPHP\Shared\* 命名空间是应用层的并发 API: Atomic、Counter、Flag、Map、Channel、Mutex、Once、Pool。 方法命名遵循一套统一的规则,用户无需为每种类型都查阅文档就能预判 API。

本文档是规范参考。新增原语以及对现有原语的改动都必须遵循它。

规则

1. 读取值——get()

PHP 惯例。Map::get()、Counter::get()、Once::get() 都使用它。

Atomic::load(Ordering $order = Ordering::SeqCst) 是有意为之的例外: 它的存在携带了 ordering 参数,表明这次读取属于内存模型契约的一部分, 有别于普通的 getter。

2. 写入值——set(),原子类型用 store()

Map::set()、Mutex 值更新(在 withLock 内部按引用进行)、Once::getOrInit()。 Atomic::store(int $value, Ordering $order = Ordering::SeqCst) 出于同样的原因与 load 对称。

3. 元素数量——count(): int

每个暴露当前大小的容器都以 count(): int 命名来实现。Channel 额外实现了 \Countable,因此 count($ch) 是获取队列中项数的原生 惯用写法。Map 将 count(): int 暴露为方法,但不实现 \Countable——需直接调用。Pool 没有 count();请通过 stats()->size() 读取其存活槽位数:

php
$ch = new OxPHP\Shared\Channel(1024); $map = new OxPHP\Shared\Map(); $pool = new OxPHP\Shared\Pool($factory); count($ch); // queued items (Channel implements \Countable) $map->count(); // entries $pool->stats()->size(); // total live slots (in-use + idle)

没有 size()、len() 或 pending()——无论实现者的肌肉记忆来自 哪种语言,这些名称在公共接口上都是禁止的。

4. 布尔 getter——is*() 前缀

Channel::isClosed()。

不用裸动词(test、check),也不用领域特定的名称(closed)。 is 前缀标记的是对某个布尔属性的纯读取。

如果某类型的状态比单个布尔值更丰富,则应将其暴露为返回枚举的 status() 方法,而非 is*() getter——Channel 的 RecvResult::status() 和 Once::status(): Once\Status (Uninitialized/Pending/Ready/Poisoned)都遵循这一点。当答案有超过 两种情况时,就应选用 status()。

Mutex 不暴露 isCorrupted()——损坏状态是黏性的、不可恢复的, 会在下次获取锁时通过 CorruptedMutexException 浮现出来。除了重新 获取锁并捕获异常之外,对这个探测结果没有任何有用的操作。

5. 等待策略三分法——try* / 裸名 / *Timeout

阻塞型原语(Channel、Mutex)通过方法名而非重载的 ?float $timeout 参数来表达等待策略:

后缀 行为 示例
try* 非阻塞;立即报告失败变体。 Channel::trySend、Channel::tryRecv、Mutex::tryWithLock
(裸名) 永久阻塞(或直到请求纤程被取消)。 Channel::send、Channel::recv、Mutex::withLock
*Timeout 有界等待。接受一个必填的 int $ms > 0。 Channel::sendTimeout、Channel::recvTimeout、Mutex::withLockTimeout

三分法把三种含义模糊的策略(null = 永久,0 = 尝试,正值 = 有界) 从一个参数里拆出来,放进三个名称自解释的方法中。

Note

*Timeout 方法上的 $ms 参数必须严格为正。零、负数、非 int 以及缺省值都会在桥接层抛出 OxPHP\Shared\TypeException。

条件成功型操作放在 Map 上,采用 setIfAbsent 的写法而非 try*:Map::setIfAbsent 仅在键不存在时提交(与 HashMap::try_insert 对应),并返回已存在的值;插入成功时返回 null。setIfAbsent 这个名称专门保留 给这一种语义;不要在别处复用它。

try* 方法如何报告失败取决于类型:Channel 返回一个值类型的 Result,Mutex 抛出 ContentionException,Pool::tryAcquire() 在池已饱和时返回 null,而 Once::trySet() 在单元已处于 Ready 或 Pending 状态时返回 false。

6. 比较并交换——compareAndSet()

Atomic::compareAndSet()、Flag::compareAndSet()。始终返回 bool(交换是否发生)。

7. 替换并返回旧值——swap()

Atomic::swap() 用于 int,Flag::swap() 用于 bool。返回旧值。

8. 原子 RMW 返回旧值——fetch*() 前缀

Atomic::fetchAdd()、fetchSub()、fetchAnd()、fetchOr()、 fetchXor()。

fetch 前缀编码了返回契约:操作前的值。这与 Counter::add() 形成对比,后者返回的是新值(LongAdder 风格的 聚合计数器)。

新增 RMW 方法时,先确定契约,再取名:

  • 返回旧值 → fetchVerb(args)
  • 返回新值 → 裸 verb(args)

不要混用。

9. 重置为默认值——clear()

Map::clear()——清空容器;返回被移除的条目数。

Counter 没有 clear()——set(0) 就是它的窗口重置。 Counter::set() 是有据可查的例外,它返回旧值(而非 void): 它是原子交换,而 set(0) 读取先前的总量正是 LongAdder 的 sumThenReset 惯用法。(Atomic 把同样的操作命名为 swap(); Counter 保留 set,因为 set($n) 用于播种和窗口化时读起来更自然。)

10. 注册表标识——id(): int

每个 Shared\* 实例都暴露 id(): int,供日志和 /__ox_shared/entry?id=<id> 可观测性端点使用。

速查表

概念 规范名称 示例
读取值 get() Map::get、Counter::get
读取原子值 load($order) Atomic::load
写入值 set() Map::set
写入原子值 store($v, $order) Atomic::store
元素数量 count(): int Map::count、Channel::count
布尔属性 is*(): bool Channel::isClosed
条件插入 setIfAbsent($k, $v) Map::setIfAbsent
非阻塞等待 try*() Channel::trySend、Mutex::tryWithLock
永久等待 裸动词 Channel::send、Channel::recv、Mutex::withLock
有界等待 *Timeout(int $ms) Channel::sendTimeout、Mutex::withLockTimeout
比较并交换 compareAndSet() Atomic::compareAndSet
交换并返回旧值 swap() Atomic::swap、Flag::swap
原子 RMW,返回旧值 fetch*() Atomic::fetchAdd
原子 RMW,返回新值 裸动词 Counter::add
重置为默认值 clear() Map::clear
注册表 id id(): int 每个 Shared\* 类型

新增 Shared\* 类型

在提议新原语时,合并前请先填写这份检查清单:

  • 每个方法都能对应到速查表中的一行,或有一份 ADR 解释其例外 (参见上文的 Atomic::load/store 和 Counter::set)。
  • 如果该类型持有值的集合,则它实现 \Countable 并暴露 count(): int。
  • 读取方法为 get 或 load(仅限原子类型)。
  • 布尔 getter 使用 is* 前缀。
  • 等待策略变体遵循 try* / 裸名 / *Timeout(int $ms) 三分法。*Timeout 变体接受 int $ms > 0,并以 TypeException 拒绝零 / 负数 / 非 int 输入。等待策略型 try* 方法要么返回一个 值类型的 Result,要么抛出领域异常——绝不用 null 编码。 Pool::tryAcquire() 在池已饱和时返回 null,不遵循这一点。 条件成功型操作遵循专用的 setIfAbsent 命名,而非 try*。
  • 没有 len、size、pending、test 或其他临时起意的名称。
  • 领域特定的动词(evict、drain、flush 等)只有在速查表中 没有任何规范条目涵盖该概念时才出现。

可观测性名称

面向运维的接口——Prometheus 指标名称以及 /__ox_shared/entry?id=<id> 处的 JSON——是与 PHP API 分离的独立 契约,因为重命名它会破坏仪表盘和告警规则。Channel 或 Pool 的元素 数量在 /metrics 上是 oxphp_shared_channel_count 或 oxphp_shared_pool_count,在其 type_specific JSON 中是 count。 对于 Channel,它就是 Channel::count() 返回的数值;对于 Pool,它是 存活槽位数,即 Pool::stats()->size() 返回的数值。

已在 0.13.0 中移除

早先的写法——oxphp_shared_channel_pending、 oxphp_shared_pool_size,以及 JSON 键 pending 和 size——已不再 发出。仍按这些名称编写的仪表盘和告警规则查不到任何序列,因此基于 它们的阈值告警永远不会触发。

稳定性

这些规则是 OxPHP\Shared\* 1.0 契约的一部分。在 1.0 发布之后, 重命名属于破坏性变更,需要一个弃用周期。在 1.0 之前,这些规则 同样具有约束力——违反它们的新方法会在评审中被拒绝。

发现错误?报告问题 →