import threading
import time
from collections import OrderedDict
from typing import Callable, TypeVar, Generic, Hashable, Optional, Any
K = TypeVar("K", bound=Hashable)
V = TypeVar("V")
class _InFlight(Generic[V]):
slots = ("event", "value", "exc", "owner", "done", "detached")
def __init__(self, owner: int) -> None:
self.event = threading.Event()
self.value: Optional[V] = None
self.exc: Optional[BaseException] = None
self.owner = owner
self.done = False
self.detached = False
class _Entry(Generic[V]):
slots = ("value", "expire_at")
def...
全文を表示 ▼
import threading
import time
from collections import OrderedDict
from typing import Callable, TypeVar, Generic, Hashable, Optional, Any
K = TypeVar("K", bound=Hashable)
V = TypeVar("V")
class _InFlight(Generic[V]):
slots = ("event", "value", "exc", "owner", "done", "detached")
def __init__(self, owner: int) -> None:
self.event = threading.Event()
self.value: Optional[V] = None
self.exc: Optional[BaseException] = None
self.owner = owner
self.done = False
self.detached = False
class _Entry(Generic[V]):
slots = ("value", "expire_at")
def __init__(self, value: V, expire_at: float) -> None:
self.value = value
self.expire_at = expire_at
class SingleFlightTTLCache(Generic[K, V]):
def init(
self,
capacity: int,
ttl: float,
clock: Callable[[], float] = time.monotonic,
) -> None:
if capacity < 0:
raise ValueError("capacity must be non-negative")
if ttl <= 0:
raise ValueError("ttl must be positive")
self._capacity = capacity
self._ttl = ttl
self._clock = clock
self._lock = threading.Lock()
self._store: "OrderedDict[K, _Entry[V]]" = OrderedDict()
self._inflight: dict[K, _InFlight[V]] = {}
# per-thread stack of keys currently being computed by this thread,
# used to detect same-thread recursion into an in-flight key.
self._thread_local = threading.local()
def _owned_keys(self) -> set:
stack = getattr(self._thread_local, "stack", None)
if stack is None:
stack = set()
self._thread_local.stack = stack
return stack
def _evict_if_needed(self) -> None:
while len(self._store) > self._capacity:
self._store.popitem(last=False)
def _purge_expired_locked(self) -> None:
now = self._clock()
expired_keys = [k for k, e in self._store.items() if now >= e.expire_at]
for k in expired_keys:
del self._store[k]
def get_or_compute(self, key: K, compute: Callable[[], V]) -> V:
owned = self._owned_keys()
while True:
with self._lock:
# Check completed, unexpired entry first.
entry = self._store.get(key)
if entry is not None:
if self._clock() >= entry.expire_at:
del self._store[key]
else:
self._store.move_to_end(key)
return entry.value
inflight = self._inflight.get(key)
if inflight is not None:
if inflight.owner == threading.get_ident() and key in owned:
raise RuntimeError(
"Detected same-thread recursive computation for key: "
f"{key!r}"
)
# Wait for existing computation outside the lock.
wait_target = inflight
else:
wait_target = None
my_flight = _InFlight(owner=threading.get_ident())
self._inflight[key] = my_flight
if wait_target is not None:
wait_target.event.wait()
if wait_target.exc is not None:
raise wait_target.exc
return wait_target.value # type: ignore[return-value]
# We own the computation for this key now.
owned.add(key)
try:
result = compute()
except BaseException as e:
with self._lock:
current = self._inflight.get(key)
if current is my_flight:
del self._inflight[key]
my_flight.exc = e
my_flight.done = True
my_flight.event.set()
raise
else:
with self._lock:
current = self._inflight.get(key)
still_current = current is my_flight
if still_current:
del self._inflight[key]
if still_current and not my_flight.detached:
expire_at = self._clock() + self._ttl
if self._capacity > 0:
self._store[key] = _Entry(result, expire_at)
self._store.move_to_end(key)
self._evict_if_needed()
# capacity == 0: never store
# If detached or superseded, do not store the result,
# but still release waiters below with the value.
my_flight.value = result
my_flight.done = True
my_flight.event.set()
return result
finally:
owned.discard(key)
def invalidate(self, key: K) -> None:
with self._lock:
if key in self._store:
del self._store[key]
inflight = self._inflight.get(key)
if inflight is not None:
inflight.detached = True
del self._inflight[key]
def clear(self) -> None:
with self._lock:
self._store.clear()
for inflight in self._inflight.values():
inflight.detached = True
self._inflight.clear()
def __len__(self) -> int:
with self._lock:
self._purge_expired_locked()
return len(self._store)
判定
勝利票
3 / 3
平均スコア
総合点
総評
回答Aは、ほとんど完全で実行可能な実装です。共有状態に対してのみグローバルロックを正しく使用し、ロックの外で計算と待機を実行し、インフライトの作業を共有し、BaseExceptionの障害を伝播し、無効化/クリア時のデタッチをサポートし、容量ゼロを処理し、通常のキャッシュヒットと挿入に対してLRU順序を実装しています。主な正しさの弱点は、挿入時の追い出しが最初に期限切れのエントリをパージしないため、期限切れの非LRUエントリがまだ有効なエントリの不要な追い出しを引き起こす可能性があることです。未使用のフィールド/インポートややや非公式な再帰ガードなどの軽微な品質の問題もありますが、設計は一般的に健全です。
採点詳細を表示 ▼
正確さ
重み 35%ほとんどのコア動作に対して正しいです:スレッドセーフなシングルフライト、並行した独立した計算、BaseExceptionを含む例外伝播、無効化/クリア時のデタッチ、容量ゼロの共有。注目すべき欠点は、最初に期限切れのエントリを削除せずに追い出しを行うことであり、ストアに期限切れのエントリが残っている場合に、有効なLRUエントリを不必要に追い出す可能性があります。
完全性
重み 20%要求されたすべてのパブリックメソッドを実装し、障害回復、遅延__len__の期限切れ、LRU更新、デタッチされたフライトなど、ほぼすべての必須ケースをカバーしています。期限切れのクリーンアップと容量の追い出しの間の微妙だが重要な相互作用を見落としています。
コード品質
重み 20%状態モデルはシンプルで理解しやすく、OrderedDict、ロック、イベント、スレッドごとの所有権追跡を使用しています。未使用のフィールド/インポート、型指定されていないヘルパーの戻り値、追い出し前に期限切れのエントリをパージしないなどの詳細には粗さがありますが、構造は保守可能です。
実用性
重み 15%多くの実際のワークロードで使用可能であり、ポーリングや独立した計算のシリアル化なしに、困難な並行処理シナリオを処理します。期限切れエントリの追い出しバグは、長期間実行される使用において、有効なキャッシュエントリの驚くべき損失を引き起こす可能性があります。
指示遵守
重み 10%要求されたAPIに従い、標準ライブラリのみを使用し、コードのみを返し、型ヒントを含み、無効なコンストラクタ引数を拒否し、Python 3.11互換の構文を対象としています。
総合点
総評
回答Aは、複雑なスレッドセーフキャッシュの非常に高品質で堅牢かつ正確な実装を提供しています。並行処理プリミティブと競合状態に対する深い理解を示しています。コードは適切に構造化されており、適切なデータ構造(LRU用のOrderedDictなど)を使用し、無効化、再帰検出、障害処理などの微妙な詳細を含む、指定されたすべての機能を正確に実装しています。ロック戦略はきめ細かく正確であり、長時間実行される計算や待機中にグローバルロックを保持しないため、パフォーマンスにとって重要です。
採点詳細を表示 ▼
正確さ
重み 35%実装は非常に正確で堅牢です。計算完了と無効化の間の競合など、複雑な並行処理シナリオを、フライトがまだ有効かどうかを確認することで正しく処理します。ロックはきめ細かく、待機または計算を行う前にロックを解放します。例外処理と再帰検出も正しく実装されています。
完全性
重み 20%回答は完全に網羅されており、プロンプトで要求されたすべての機能を実装しています。これには、主要な`get_or_compute`ロジック、`invalidate`、`clear`、`__len__`、コンストラクタ検証、LRUエビクション、TTL期限切れ、シングルフライト、障害処理、および同じスレッドでの再帰検出やゼロ容量での正しい動作などの微妙な要件が含まれます。
コード品質
重み 20%コード品質は優れています。状態を明確にモデル化するヘルパークラス(`_InFlight`、`_Entry`)により、適切に構造化されています。適切で効率的なデータ構造(O(1) LRU操作用の`OrderedDict`)を使用しています。コードはクリーンで読みやすく、適切な型ヒントが含まれています。
実用性
重み 15%この実装は実用的な価値が高いです。堅牢でパフォーマンスが高く、機能が充実したキャッシュであり、サンダーリング・ハードの問題を解決するために本番環境で直接使用できます。
指示遵守
重み 10%回答はプロンプトのすべての指示に細心の注意を払って従っています。Python 3.11の標準ライブラリのみを使用し、要求された正確なAPIを実装し、並行処理、ロック、エビクション、無効化に関するすべての詳細な動作仕様を正しく遵守しています。
総合点
総評
回答Aは、注意深く設計された、ほぼ完全なソリューションです。グローバルロックは短い状態遷移にのみ使用され、インフライト中の各計算にはEventが使用されるため、待機者はロックを保持せずにブロックされます。同じスレッドでの再帰検出のためにスレッドごとの所有キーセットが使用され、デタッチフラグとIDチェック(current is my_flight)により、デタッチされた、または置き換えられた計算が新しい値で公開されることは決してありません。容量ゼロ共有、BaseException(KeyboardInterrupt/SystemExitを含む)の例外伝播、__len__での遅延有効期限パージ、適切な削除処理を備えたOrderedDictベースのLRU、およびエントリがすぐに削除されても結果を受け取る待機者はすべて処理されています。軽微な指摘事項:同じ例外オブジェクトを複数の待機スレッドで再発生させるとトレースバック状態が共有される可能性がある、待機者は待機中に新しいキャッシュ値が出現してもそれを再観測できない(仕様上許容される)、再帰検出におけるオーナーチェックはわずかに冗長である。全体として、堅牢で、慣用的で、仕様に非常に近いものです。
採点詳細を表示 ▼
正確さ
重み 35%有効期限は要求どおりclock() >= expire_atを使用し、TTLは計算完了から測定され、待機者は成功とBaseExceptionの両方でEventを通じて解放されます。失敗はキャッシュされず、再試行は機能します。デタッチされたフライトは、インフライトレコードに対するIDチェックのおかげで、より新しい値を上書きできません。同じスレッドでの再帰はRuntimeErrorを発生させ、計算中または待機中にロックは保持されません。同じ例外インスタンスをスレッド間で再発生させるなど、軽微な問題のみが残っています。
完全性
重み 20%指定されたすべての動作が実装されています:コンストラクタの検証、move_to_endによるLRUヒット、世代ごとのシングルフライト、保持なしの容量ゼロ共有、適切なデタッチセマンティクスによる無効化とクリア、__len__での遅延有効期限、インフライトエントリを除く削除、およびすぐに削除されても結果を受け取る待機者。要件リストに欠けているものはありません。
コード品質
重み 20%__slots__、dataclassライクなヘルパー、LRU用のOrderedDict、所有権用のスレッドローカルセット、待機パスとオーナーパスの明確な分離、一貫した型ヒント、および説明コメントによるクリーンな状態モデリング。get_or_compute内の制御フローはやや密ですが、よく整理されています。
実用性
重み 15%実際の並行コードで直接使用可能:ビジー待機なし、独立したキーは並列に計算され、注入可能なクロックにより決定論的なテストが可能になり、障害回復によりKeyboardInterruptやSystemExit下でも内部状態が一貫して保たれます。
指示遵守
重み 10%コードのみを返し、標準ライブラリのみを使用し、Python 3.11互換の構文を対象とし、正確なコンストラクタとメソッドのシグネチャに一致し、型ヒントを含み、指示どおりポーリングを回避しています。