Shared\Flag
OxPHP\Shared\Flag 是一个进程级的原子布尔值——它是 Shared\Atomic 的布尔版孪生体。每个操作都是无锁的;两个工作进程并发翻转该标志时,绝不会观察到中间状态。
概述
- 原子布尔。 单个比特的状态,配有
load/store/swap/compareAndSet。 - 显式内存序。 每个操作都接受一个可选的
Ordering,默认为SeqCst,与Shared\Atomic完全一致。 - 无锁。 所有写入都是单条 CPU 原子指令。在竞争下也安全。
- 可共享。 实例存活于注册表中,可以存入
Shared\Map、通过use捕获传递等。
API 参考
namespace OxPHP\Shared;
final class Flag implements Shareable
{
public function __construct(bool $initial = false);
public function load(Ordering $order = Ordering::SeqCst): bool; // Relaxed | Acquire | SeqCst
public function store(bool $value, Ordering $order = Ordering::SeqCst): void; // Relaxed | Release | SeqCst
public function swap(bool $value, Ordering $order = Ordering::SeqCst): bool; // any ordering; returns previous
public function compareAndSet(
bool $expect,
bool $new,
Ordering $success = Ordering::SeqCst,
Ordering $failure = Ordering::SeqCst, // Relaxed | Acquire | SeqCst
): bool;
public function id(): int;
}| 方法 | 返回值 | 使用场景 |
|---|---|---|
load |
当前值 | 纯读取。 |
store |
void | 无条件设置为一个显式的值。 |
swap |
先前值 | 设置为一个显式的值;返回值告诉你是否改变了它。swap(true) 就是 test-and-set("是我赢了吗?")。 |
compareAndSet |
是否交换 | 一次性初始化:仅当标志为期望值时才成功。 |
示例
紧急开关
<?php
use OxPHP\Shared\Flag;
$maintenance = new Flag();
// In a request handler
if ($maintenance->load()) {
http_response_code(503);
header('Retry-After: 60');
echo 'under maintenance';
return;
}
// In an admin endpoint
$maintenance->store(true); // enable
$maintenance->store(false); // disable一次性初始化的胜出者
<?php
use OxPHP\Shared\Flag;
$migrated = new Flag();
if ($migrated->compareAndSet(expect: false, new: true)) {
// First worker to arrive wins — run the migration once.
runSchemaMigration();
} else {
// Someone else already ran it.
}熔断器跳闸
<?php
use OxPHP\Shared\Flag;
$tripped = new Flag();
try {
callDownstream();
} catch (DownstreamFailedException $e) {
$wasAlreadyTripped = $tripped->swap(true); // set true, learn the prior state
if (!$wasAlreadyTripped) {
alertOncall($e); // fire alert only on first trip
}
throw $e;
}要实现一个完整的熔断器,你通常会需要一个 Shared\Counter 来记录失败窗口,以及一个 Shared\Flag 来表示跳闸状态——当窗口冷却下来后,通过 store(false) 重置该标志。
先发布负载(请求体),再用更廉价的内存序发出信号
<?php
use OxPHP\Shared\Flag;
use OxPHP\Shared\Map;
use OxPHP\Shared\Ordering;
$ready = new Flag();
$config = new Map();
// Producer: write the payload, then publish with Release.
$config->set('dsn', $dsn);
$ready->store(true, Ordering::Release);
// Consumer: an Acquire load that observes `true` also observes the payload.
if ($ready->load(Ordering::Acquire)) {
$dsn = $config->get('dsn');
}语义与注意事项
swap 返回的是先前的值,这也是最有用的返回值:"我改变了什么吗?"即 $prev !== $new,而 swap(true) 正是标准的 test-and-set。store 返回 void;如果你需要先前的值,请使用 swap。
compareAndSet 是表达"第一个赢家胜出"的方式。 单纯的 store(true) 总会成功,因此它无法表达"若已设置则不覆盖"。
内存序与 Shared\Atomic 一致。load 拒绝 Release/AcqRel,store 拒绝 Acquire/AcqRel,而 compareAndSet 的 $failure 拒绝 Release/AcqRel——每种情况都会抛出 InvalidOrderingException。默认的 SeqCst 始终是安全的。
Flag 不会阻塞。如果你需要等待某个状态转换,请将它与 Shared\Channel 搭配使用,或改用 Shared\Once。
异常
| 异常 | 触发方 |
|---|---|
StaleHandleException |
在其注册表条目已被逐出的句柄上调用的任何方法。 |
UninitializedException |
在尚未完成 __construct 的包装器上调用 id()。 |
InvalidOrderingException |
该操作不允许的 Ordering(见上文)。 |
可观测性
参见 Shared 可观测性。快速参考:
GET /__ox_shared/entry?id=N暴露{ value: true|false, type: "Flag" }。- Prometheus
oxphp_shared_flag_value{flag_id="…"}仪表盘(0 或 1)。 - 注册表级别的指标通过
type="Flag"标签覆盖 Flag。
何时不该使用
- 多状态逻辑。 Flag 只有两个取值。如果你需要 idle/busy/done 或任何三态机,请改用
Shared\Counter(使用整数枚举值)或在类枚举数组上使用Shared\Mutex。 - 等待状态转换。 Flag 不会阻塞。当某个工作进程应当等待标志翻转时,请与
Shared\Channel(或一个你用compareAndSet轮询的Shared\Counter)搭配使用。 - 计数事件。 Flag 不是计数器。请用
Shared\Counter来做计数。 - 整数状态。 如果这个开关其实是一个小整数,请直接使用
Shared\Atomic。
相关内容
- Shared State —— 概述与心智模型。
- Shared\Atomic —— int64 版孪生体,相同的内存序模型。
- Shared\Counter —— 当你需要的不止是开/关时。
- Shared\Once —— 当只计算一次的值比布尔更丰富时。
- Shared\Mutex —— 当标志翻转必须与其他状态一同提交时。