Shared\Counter

OxPHP\Shared\Counter は、累積に特化した、プロセス全体で共有される64ビット符号付きアトミック整数です。イベントのカウント、デルタの合計、ローリングウィンドウの集計などに使います。すべての操作はロックフリーで、2つのワーカーが並行して加算してもカウントを取りこぼすことはありません。

他のメモリを同期させる必要がある任意のアトミックな状態(ステートマシン、バージョンスタンプ、seqlock、ビットフラグマスク)には、代わりに Shared\Atomic を使ってください。

概要

  • アトミックな int64。 範囲は −9_223_372_036_854_775_808 … 9_223_372_036_854_775_807 です。オーバーフローはラップアラウンドします。
  • ロックフリー。 add は単一の fetch_add にコンパイルされます。
  • 常に Relaxed。 各操作はアトミック(カウントの取りこぼしも torn read もない)ですが、他のメモリとの間に happens-before を確立しません。Counter は統計情報であって同期ポイントではありません。オーダリングが必要な場合は Shared\Atomic を使ってください。
  • 共有可能。 インスタンスは Shared\Map / Shared\Channel の中に格納したり、use キャプチャを介してファイバーに渡したりできます。

API リファレンス

php
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
<?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
<?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
<?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
<?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) は任意の新しい開始値を設定します。

Relaxed なオーダリング

各操作はアトミックですが、Counter は他のメモリを公開しません。書き込み側が整数をインクリメントする に書き込んだデータを読み取り側が必ず観測しなければならないなら、それは同期です。その場合は Shared\AtomicOrdering::Release/Acquire とともに使ってください。

compareAndSet は Relaxed/Relaxed であり、オーダリングの引数を取りません。カウンター自身の値に基づく判断(上限、下限、値による確保)には正しく機能します。他の状態を公開する CAS は Shared\Atomic の役割です。

オーバーフローはラップアラウンドします

INT_MAX を超えて加算すると INT_MIN に戻ります。毎秒数千回のペースで数か月にわたって動作するカウンターでは、値を数十兆の範囲に収めるか、定期的にリセットしてください。

小数値は扱えません。 浮動小数点精度の平均を求めるためにバイト数をカウントしていますか? 分子(Counter)と分母(Counter)を別々に追跡し、読み取り時に除算してください。

例外

例外 発生元
StaleHandleException レジストリエントリが削除されたハンドルに対する任意のメソッド。
UninitializedException __construct が完了していないラッパーに対する id()

Counter はオーバーフローや極端な値で例外を投げることはなく、ラップアラウンドします。

可観測性

完全な解説は Shared Observability を参照してください。クイックリファレンス:

  • GET /__ox_shared/entry?id=N{ value, type: "Counter" } を公開します。
  • Prometheus の oxphp_shared_counter_value{counter_id="…"} ゲージが現在値を追跡します。
  • レジストリ全体のカウンター(oxphp_shared_operations_totaloxphp_shared_objects_total)は、type="Counter" ラベルを介して Counter をカバーします。

使うべきでない場面

  • 浮動小数点数または小数。 Counter のペア(分子/分母)、または Shared\Mutex<array{total_cents: int, count: int}> を使ってください。
  • 豊富なコンテキストを必要とする非数値イベント。 {count, last_actor, last_reason} を1つのキーに紐付ける必要がある場合は、Shared\Map または Shared\Mutex を使ってください。
  • ホストをまたぐ集計。 Counter はプロセス内に限定されます。複数ホストにわたる集計には、メトリクスパイプライン(Prometheus + rate()、または中央の Redis INCR)を使ってください。
  • 永続性。 Counter の状態はサーバー停止時に消失します。合計値が再起動をまたいで残る必要がある場合は、スナップショットを別の場所に永続化してください。

関連項目

  • Shared State — 概要と移行パターン。
  • Shared\Atomic — CAS、swap、完全なメモリオーダリング制御を備えた汎用のアトミックな int64。
  • Shared\Map — カウントにキーが付く場合(Map<string, Counter>)。
  • Shared\Flag — 値が単なるオン/オフの場合。
  • Shared\Mutex — カウンターが他のフィールドと歩調を合わせて更新される必要がある場合。