Shared\Counter
OxPHP\Shared\Counter 是一个进程级的原子 64 位有符号整数,专为累加而设计:统计事件、累加增量、滚动窗口总计。每个操作都是无锁的;两个工作进程并发相加时绝不会丢失任何一次计数。
如果需要任意的原子状态且必须同步其他内存(状态机、版本戳、seqlock、位标志掩码),请改用 Shared\Atomic。
概述
- **原子 int64。**取值范围
−9_223_372_036_854_775_808 … 9_223_372_036_854_775_807。溢出会回绕。 - 无锁。
add会编译为单条fetch_add。 - **始终是 Relaxed。**操作是原子的(不会丢失计数,不会出现撕裂读),但与其他内存之间不建立 happens-before 关系。Counter 是统计量,而非同步点——如果需要顺序保证,请使用
Shared\Atomic。 - **可共享。**实例可以存放在
Shared\Map/Shared\Channel中,并通过use捕获传递给纤程。
API 参考
namespace OxPHP\Shared;
final class Counter implements Shareable
{
public function __construct(int $initial = 0);
public function get(): int; // current
public function set(int $value): int; // returns previous; set(0) = window reset
public function add(int $delta = 1): int; // returns new; add()=+1, add(-1)=decrement
public function compareAndSet(int $expect, int $new): bool;
public function id(): int;
}| 方法 | 返回值 | 用途 |
|---|---|---|
get |
当前值 | 读取而不修改。 |
set |
前一个值 | 原子交换;set(0) 是窗口结束时的读取并清零。 |
add |
新值 | add() 加 1,add(-1) 递减,其他增量则加对应值。 |
compareAndSet |
bool | 通过 CAS 循环实现有界/饱和计数器(上限、下限)。 |
id |
注册表 id | 日志、追踪、/__ox_shared/entry?id=… 关联。 |
示例
每个工作进程的请求计数器
<?php
$requests = new OxPHP\Shared\Counter();
oxphp_worker(function () use ($requests) {
$count = $requests->add(); // +1, returns the new total
header("X-Request-Count: {$count}");
echo "ok";
});窗口滚动
<?php
$hits = new OxPHP\Shared\Counter();
// Every N minutes in your cron/worker loop:
$prev = $hits->set(0); // atomically reads and zeroes
logWindowMetric($prev);有界计数器(CAS 循环)
<?php
$slots = new OxPHP\Shared\Counter();
$cap = 100;
// Claim a slot only while under the cap.
do {
$cur = $slots->get();
if ($cur >= $cap) {
// full — reject
break;
}
} while (!$slots->compareAndSet($cur, $cur + 1));批量累加
<?php
$bytes = new OxPHP\Shared\Counter();
// Sum a batch in PHP, then one atomic add (one FFI call).
$deltas = array_map(fn ($req) => strlen($req['body']), $batch);
$newTotal = $bytes->add(array_sum($deltas));语义与注意事项
set() 会先返回前一个值,然后再存入——整个过程是原子的。set(0) 是快照并清零(LongAdder::sumThenReset)模式;set($n) 则设定任意新的起始值。
每个操作都是原子的,但 Counter 不会发布其他内存。如果读取方必须观察到写入方在递增该整数之前所写的数据,那就是同步——请使用带 Ordering::Release/Acquire 的 Shared\Atomic。
compareAndSet 采用 Relaxed/Relaxed,不接受任何顺序参数。对于基于计数器自身取值做出的决策(上限、下限、按值占用),它是正确的。而需要发布其他状态的 CAS 则属于 Shared\Atomic。
加过 INT_MAX 后会回绕到 INT_MIN。对于以每秒数千次的速率连续运行数月的计数器,请将其值保持在数十万亿的范围内,或定期重置。
**不支持小数值。**为了得到浮点精度的平均值而在统计字节数?请分别用两个 Counter 记录分子和分母,在读取时再做除法。
异常
| 异常 | 抛出场景 |
|---|---|
StaleHandleException |
在注册表条目已被驱逐的句柄上调用任何方法。 |
UninitializedException |
在尚未完成 __construct 的包装器上调用 id()。 |
对于溢出或极端值,Counter 永远不会抛出异常——它会回绕。
可观测性
完整介绍参见 Shared Observability。快速参考:
GET /__ox_shared/entry?id=N会暴露{ value, type: "Counter" }。- Prometheus 的
oxphp_shared_counter_value{counter_id="…"}gauge 会跟踪当前值。 - 注册表级别的计数器(
oxphp_shared_operations_total、oxphp_shared_objects_total)通过type="Counter"标签覆盖 Counter。
何时不该使用
- **浮点数或小数。**请使用一对 Counter(分子/分母),或
Shared\Mutex<array{total_cents: int, count: int}>。 - **需要丰富上下文的非数值事件。**如果你需要将
{count, last_actor, last_reason}绑定到一个键上,请选用Shared\Map或Shared\Mutex。 - **跨主机汇总。**Counter 仅限进程内使用。对于多主机聚合,请使用指标管道(Prometheus +
rate(),或集中式的 RedisINCR)。 - **持久性。**服务器停止时 Counter 的状态会消失。如果总数必须在重启后保留,请将快照持久化到别处。
相关内容
- 共享状态——概述与迁移模式。
- Shared\Atomic——通用的原子 int64,支持 CAS、swap 以及完整的内存顺序控制。
- Shared\Map——当计数需要按键区分时(
Map<string, Counter>)。 - Shared\Flag——当值只是开/关时。
- Shared\Mutex——当计数器必须与其他字段同步更新时。