Shared\Flag

OxPHP\Shared\Flag 是一个进程级的原子布尔值——它是 Shared\Atomic 的布尔版孪生体。每个操作都是无锁的;两个工作进程并发翻转该标志时,绝不会观察到中间状态。

概述

  • 原子布尔。 单个比特的状态,配有 load / store / swap / compareAndSet
  • 显式内存序。 每个操作都接受一个可选的 Ordering,默认为 SeqCst,与 Shared\Atomic 完全一致。
  • 无锁。 所有写入都是单条 CPU 原子指令。在竞争下也安全。
  • 可共享。 实例存活于注册表中,可以存入 Shared\Map、通过 use 捕获传递等。

API 参考

php
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
<?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
<?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
<?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
<?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/AcqRelstore 拒绝 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 —— 当标志翻转必须与其他状态一同提交时。