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 = null) 是有意为之的例外:
它的存在携带了 ordering 参数,表明这次读取属于内存模型契约的一部分,
有别于普通的 getter。
2. 写入值——set(),原子类型用 store()
Map::set()、Mutex 值重置(通过 with)、Once::getOrInit()。
Atomic::store($value, ?Ordering) 出于同样的原因与 load 对称。
3. 元素数量——count(): int
每个暴露当前大小的容器都以 count(): int 命名来实现。Channel
额外实现了 \Countable,因此 count($ch) 是获取队列中项数的原生
惯用写法。Map 和 Pool 将 count(): int 暴露为方法,但不实现
\Countable——需直接调用:
$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->count(); // 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 = 尝试,正值 = 有界)
从一个参数里拆出来,放进三个名称自解释的方法中。
*Timeout 方法上的 $ms 参数必须严格为正。零、负数、非
int 以及缺省值都会在桥接层抛出 OxPHP\Shared\TypeException。
条件成功型操作放在 Map 上,采用 setIfAbsent 的写法而非
try*:Map::setIfAbsent 仅在键不存在时提交,并返回 bool
(与 HashMap::try_insert 对应)。setIfAbsent 这个名称专门保留
给这一种语义;不要在别处复用它。
try* 的统一不变式:它要么返回一个值类型的 Result(Channel),
要么抛出 ContentionException(Mutex)。它绝不会返回 null 来
编码「未成功」。那是旧 API 的做法,会产生 null 合并的歧义,而三分法
正是要消除这种歧义。
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()——清空容器;返回 void。
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、Pool::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编码。条件成功型 操作遵循专用的setIfAbsent命名,而非try*。 - 没有
len、size、pending、test或其他临时起意的名称。 - 领域特定的动词(
evict、drain、flush等)只有在速查表中 没有任何规范条目涵盖该概念时才出现。
可观测性名称滞后于 PHP API
面向运维的接口——Prometheus 指标名称以及
/__ox_shared/entry?id=<id> 处的 JSON——是与 PHP API 分离的独立
契约。重命名它会破坏仪表盘和告警规则。为避免悄无声息的不一致,
受影响的名称会在一个发布周期内同时发出两份:
| 接口 | 已弃用(仍在发出) | 规范名称 |
|---|---|---|
| Prometheus | oxphp_shared_channel_pending |
oxphp_shared_channel_count |
| Prometheus | oxphp_shared_pool_size |
oxphp_shared_pool_count |
| JSON entry | Channel.pending |
Channel.count |
| JSON entry | Pool.size |
Pool.count |
已弃用指标的 # HELP 行会带有 (deprecated, removed in a future release; use *_count) 前缀,并且只要开启了内省或指标,
ox_shared 插件就会在启动时发出一条 WARN。
请在弃用周期结束前,把仪表盘和告警规则迁移到 _count 名称。
移除之后将只发出规范名称,引用旧名称的 Prometheus/Grafana 面板
将开始返回空序列。
稳定性
这些规则是 OxPHP\Shared\* 1.0 契约的一部分。在 1.0 发布之后,
重命名属于破坏性变更,需要一个弃用周期。在 1.0 之前,这些规则
同样具有约束力——违反它们的新方法会在评审中被拒绝。