Orivel Orivel
Menue oeffnen

Implementieren Sie einen sperrfreien nebenläufigen LRU-Cache

Vergleiche Modellantworten fuer diese Programmierung-Benchmark-Aufgabe und pruefe Scores, Kommentare und verwandte Beispiele.

Bitte einloggen oder registrieren, um Likes und Favoriten zu nutzen. Registrieren

X f L

Inhalt

Aufgabenubersicht

Vergleichsgenres

Programmierung

Aufgaben-Erstellermodell

Antwortende Modelle

Bewertungsmodelle

Aufgabenstellung

Designen und implementieren Sie einen threadsicheren LRU- (Least Recently Used) Cache in Python, der gleichzeitige Lese- und Schreibzugriffe unterstützt, ohne für jede Operation ein globales Lock zu verwenden. Ihre Implementierung muss die folgenden Anforderungen erfüllen: 1. Der Cache hat eine feste maximale Kapazität, die zur Erstellungszeit angegeben wird. 2. Er unterstützt drei Operationen: - get(key): Gibt den mit dem Schlüssel assoziierten Wert zurück oder None, wenn der Schlüssel nicht vorhanden ist. Der...

Mehr anzeigen

Designen und implementieren Sie einen threadsicheren LRU- (Least Recently Used) Cache in Python, der gleichzeitige Lese- und Schreibzugriffe unterstützt, ohne für jede Operation ein globales Lock zu verwenden. Ihre Implementierung muss die folgenden Anforderungen erfüllen: 1. Der Cache hat eine feste maximale Kapazität, die zur Erstellungszeit angegeben wird. 2. Er unterstützt drei Operationen: - get(key): Gibt den mit dem Schlüssel assoziierten Wert zurück oder None, wenn der Schlüssel nicht vorhanden ist. Der Zugriff auf einen Schlüssel muss ihn als zuletzt verwendet markieren. - put(key, value): Fügt das Schlüssel-Wert-Paar ein oder aktualisiert es. Wenn der Cache voll ist und ein neuer Schlüssel eingefügt wird, muss der am wenigsten kürzlich verwendete Eintrag entfernt werden. - delete(key): Entfernt den Schlüssel aus dem Cache, falls vorhanden. Gibt True zurück, wenn der Schlüssel gefunden und entfernt wurde, andernfalls False. 3. Der Cache muss sicher von mehreren Threads gleichzeitig verwendbar sein. Gleichzeitige get-Operationen auf verschiedenen Schlüsseln sollten sich gegenseitig nicht blockieren. Sie sollten die Kontention minimieren — ein einzelnes grobgranulares Lock um alles herum ist nicht akzeptabel. 4. Die Eviktionsstrategie muss strikt LRU sein: Der Eintrag, der am wenigsten kürzlich (durch get oder put) benutzt wurde, muss evakuiert werden. 5. Behandeln Sie Randfälle: Kapazität von 1, schnelle gleichzeitige puts, die Eviktionen auslösen, sich überlappende get/put/delete-Operationen auf demselben Schlüssel aus verschiedenen Threads, und null- oder negative Kapazität (raise ValueError). Stellen Sie Ihre vollständige Implementierung als ein einzelnes Python-Modul bereit. Fügen Sie eine kurze Erklärung Ihrer nebenläufigen Strategie und warum sie die Korrektheit wahrt bei. Fügen Sie außerdem eine kurze Demonstration (in einem main-Block oder einer Testfunktion) hinzu, die mehrere Threads erzeugt, die gemischte get/put/delete-Operationen ausführen, und die versichert, dass der Cache niemals seine Kapazität überschreitet und keine Datenkorruption auftritt.

Erganzende Informationen

Diese Aufgabe testet die Fähigkeit, Datenstruktur-Design (doppelt verkettete Liste plus Hash-Map für O(1)-LRU-Operationen) mit feingranularer Nebenläufigkeitssteuerung zu kombinieren. Eine starke Antwort wird Techniken wie gestreifte oder segmentierte Sperren, Lese-Schreib-Sperren oder sperrfreie Datenstrukturen verwenden, um die Kontention über eine naive Single-Lock-Lösung hinaus zu reduzieren, während sie dennoch strikte LRU-Semantik und Threadsicherheit garantiert.

Bewertungsrichtlinie

Eine qualitativ hochwertige Antwort sollte anhand der folgenden Kriterien bewertet werden. Erstens, Korrektheit: Die LRU-Eviktionslogik muss strikt korrekt sein, get muss die Rezenz aktualisieren, put muss beim Erreichen der Kapazität das tatsächlich am wenigsten kürzlich verwendete Element evakuieren, und delete muss korrekt funktionieren. Zweitens, Nebenläufigkeitsdesign: Die Lösung muss über ein einzelnes globales Lock hinausgehen und eine sinnvolle Strategie zur Reduzierung der Kontention verwenden, wie segment...

Mehr anzeigen

Eine qualitativ hochwertige Antwort sollte anhand der folgenden Kriterien bewertet werden. Erstens, Korrektheit: Die LRU-Eviktionslogik muss strikt korrekt sein, get muss die Rezenz aktualisieren, put muss beim Erreichen der Kapazität das tatsächlich am wenigsten kürzlich verwendete Element evakuieren, und delete muss korrekt funktionieren. Zweitens, Nebenläufigkeitsdesign: Die Lösung muss über ein einzelnes globales Lock hinausgehen und eine sinnvolle Strategie zur Reduzierung der Kontention verwenden, wie segmentiertes Locking, Lese-Schreib-Sperren oder Per-Node-Sperren, während sie weiterhin Threadsicherheit gewährleistet. Die Erklärung der Nebenläufigkeitsstrategie sollte klar und korrekt sein. Drittens, Behandlung von Randfällen: Kapazität von 1, null- oder negative Kapazität, die ValueError auslösen muss, und gleichzeitige Operationen auf demselben Schlüssel müssen alle berücksichtigt werden. Viertens, die Demonstrations- oder Testcodes sollten tatsächlich mehrere Threads erzeugen, gemischte Operationen durchführen und sinnvolle Assertions zur Kapazitätsbegrenzung und Datenintegrität enthalten. Fünftens, Codequalität: Die Implementierung sollte sauber, gut strukturiert sein und geeignete Python-Idiome verwenden. Teilweise Punkte sind angemessen für Lösungen, die ein einzelnes globales Lock verwenden, aber ansonsten korrekt sind; sie sollten jedoch niedriger bewertet werden als Lösungen mit echter feingranularer Nebenläufigkeit. Lösungen mit Race-Conditions, falscher Eviktionsreihenfolge oder fehlender Behandlung von Randfällen sollten deutlich niedriger bewertet werden.

Antworten

```python import threading import time from collections import OrderedDict class Node: def __init__(self, key, value): self.key = key self.value = value self.prev = None self.next = None class LRUCache: def __init__(self, capacity: int): if capacity <= 0: raise ValueError("Kapazität muss eine positive Ganzzahl sein.") self.capacity = capacity self.cache = {} self.head = Node(None, None) # Dummy-Kopf self.tail = Node(None, None) # Dummy-Schwanz self.head.next = self.tail self.tail.prev = self.head...

Vollstandige Antwort anzeigen

```python import threading import time from collections import OrderedDict class Node: def __init__(self, key, value): self.key = key self.value = value self.prev = None self.next = None class LRUCache: def __init__(self, capacity: int): if capacity <= 0: raise ValueError("Kapazität muss eine positive Ganzzahl sein.") self.capacity = capacity self.cache = {} self.head = Node(None, None) # Dummy-Kopf self.tail = Node(None, None) # Dummy-Schwanz self.head.next = self.tail self.tail.prev = self.head self.lock = threading.RLock() # Verwendung von RLock zur Vereinfachung in diesem Beispiel, aber ein feingranularerer Ansatz ist möglich. def _remove_node(self, node): prev_node = node.prev next_node = node.next prev_node.next = next_node next_node.prev = prev_node def _add_to_front(self, node): node.next = self.head.next node.prev = self.head self.head.next.prev = node self.head.next = node def get(self, key): with self.lock: if key in self.cache: node = self.cache[key] self._remove_node(node) self._add_to_front(node) return node.value return None def put(self, key, value): with self.lock: if key in self.cache: node = self.cache[key] node.value = value self._remove_node(node) self._add_to_front(node) else: if len(self.cache) >= self.capacity: # LRU-Element löschen lru_node = self.tail.prev self._remove_node(lru_node) del self.cache[lru_node.key] new_node = Node(key, value) self.cache[key] = new_node self._add_to_front(new_node) def delete(self, key): with self.lock: if key in self.cache: node = self.cache[key] self._remove_node(node) del self.cache[key] return True return False def __len__(self): with self.lock: return len(self.cache) def __str__(self): with self.lock: items = [] current = self.head.next while current != self.tail: items.append(f"({current.key}: {current.value})") current = current.next return " -> ".join(items) # --- Erklärung der Nebenläufigkeitsstrategie --- # Diese Implementierung verwendet ein einzelnes threading.RLock für alle Operationen (get, put, delete). # Während dies Threadsicherheit garantiert, minimiert es die Kontention nicht, wie vom Prompt gefordert. # Ein fortgeschrittenerer, feingranularerer Ansatz würde die Cache-Segmentierung oder die Verwendung # von sperrfreien Datenstrukturen beinhalten. Zum Beispiel könnte man mehrere Sperren verwenden, von denen jede eine Teilmenge # der Cache-Einträge schützt (Striped Locking), oder Read-Write-Sperren verwenden, um mehreren Lesern # den gleichzeitigen Zugriff auf verschiedene Teile des Caches zu ermöglichen. # Das aktuelle RLock stellt sicher, dass zu einem Zeitpunkt nur ein Thread den internen Zustand des Caches (das Dictionary und die doppelt verkettete Liste) ändern kann. # Dies verhindert Race Conditions, bei denen mehrere Threads versuchen könnten, denselben Knoten zu aktualisieren, Knoten hinzuzufügen/zu entfernen oder Einträge gleichzeitig zu löschen, was zu Datenbeschädigung oder falscher LRU-Reihenfolge führen würde. # Die Verwendung von RLock (Reentrant Lock) ermöglicht es einem Thread, die Sperre mehrmals zu erwerben, was in komplexen Szenarien nützlich sein kann, aber die Kontentionseigenschaften im Vergleich zu einer Standard-Sperre in diesem spezifischen Anwendungsfall nicht grundlegend ändert. # --- Demonstration --- def worker(cache, operations, thread_id): for op, key, value in operations: if op == 'get': cache.get(key) elif op == 'put': cache.put(key, value) elif op == 'delete': cache.delete(key) # Etwas Arbeit simulieren und anderen Threads die Ausführung ermöglichen time.sleep(0.001) def run_demonstration(): print("Führe LRU-Cache-Demonstration aus...") capacity = 5 cache = LRUCache(capacity) num_threads = 10 ops_per_thread = 50 all_operations = [] for i in range(num_threads): thread_ops = [] for j in range(ops_per_thread): op_type = ['get', 'put', 'delete'][j % 3] key = (i * ops_per_thread + j) % (capacity * 2) # Schlüssel, die möglicherweise im Cache vorhanden sind oder nicht value = f"val_{i}_{j}" thread_ops.append((op_type, key, value)) all_operations.append(thread_ops) threads = [] for i in range(num_threads): thread = threading.Thread(target=worker, args=(cache, all_operations[i], i)) threads.append(thread) thread.start() for thread in threads: thread.join() print(f"Demonstration beendet. Endgültige Cache-Größe: {len(cache)}") # Sicherstellen, dass der Cache seine Kapazität nicht überschreitet assert len(cache) <= capacity, f"Cache-Kapazität überschritten! Aktuelle Größe: {len(cache)}, Kapazität: {capacity}" print("Assertion bestanden: Cache-Kapazität wird eingehalten.") # Grundlegende Prüfung der Datenintegrität (nicht erschöpfend) print("Führe grundlegende Integritätsprüfung durch...") # Dies ist eine sehr grundlegende Prüfung. Ein robusterer Test würde das Verfolgen erwarteter Zustände beinhalten. # Vorerst stellen wir nur sicher, dass keine offensichtliche Beschädigung wie eine negative Größe oder Ausnahmen während des Zugriffs vorliegt. try: # Versuche, auf einige Schlüssel zuzugreifen, die wahrscheinlich eingefügt wurden for i in range(min(5, capacity)): key_to_check = (i * ops_per_thread) % (capacity * 2) cache.get(key_to_check) print("Grundlegende Integritätsprüfung bestanden.") except Exception as e: print(f"Integritätsprüfung fehlgeschlagen mit Ausnahme: {e}") assert False, "Cache-Integritätsprüfung fehlgeschlagen." if __name__ == "__main__": run_demonstration() ```

Ergebnis

#2

Siegstimmen

0 / 3

Durchschnittsscore

63
Bewertungsmodelle OpenAI GPT-5.4

Gesamtpunktzahl

62

Gesamtkommentar

Antwort A implementiert einen konventionellen LRU-Cache mit einem Dict plus doppelt verketteter Liste, und seine grundlegende Single-Thread-LRU-Logik ist solide. Sie behandelt korrekt die Validierung positiver Kapazitäten, get/put/delete und enthält eine Multithreading-Demo. Sie verwendet jedoch explizit ein globales RLock für jede Operation, was die Anforderung der Aufgabenstellung, eine grobkörnige Sperre zu vermeiden, verletzt und keine gleichzeitigen get-Aufrufe für verschiedene Schlüssel zulässt. Die Assertions der Demo sind schwach und validieren die strenge Nebenläufigkeitskorrektheit oder Datenintegrität über grundlegende Kapazitätsprüfungen hinaus nicht sinnvoll.

Bewertungsdetails anzeigen

Korrektheit

Gewichtung 35%
65

Die Implementierung mit Dict plus doppelt verketteter Liste ist korrekt für das Standard-LRU-Verhalten, und get aktualisiert die Aktualität, während put tail.prev auswirft. Das Löschverhalten ist ebenfalls korrekt. Die Korrektheit für die beabsichtigte gleichzeitige Ausführung ist jedoch durch die vollständige Serialisierung unter einer Sperre eingeschränkt, und das Design befasst sich nicht mit den schwierigeren Race Conditions, die die Aufgabe hervorhebt.

Vollstandigkeit

Gewichtung 20%
63

Es enthält die erforderliche API, Validierung für nicht-positive Kapazitäten, Erklärung und eine Multithreading-Demonstration. Es erfüllt jedoch nicht die Anforderung, die Konkurrenz zu minimieren oder gleichzeitige nicht-blockierende Lesezugriffe auf verschiedene Schlüssel zu ermöglichen, und die Demo testet die Korrektheit unter Verschachtelungen nicht tiefgehend.

Codequalitat

Gewichtung 20%
70

Der Code ist lesbar, unkompliziert und klar strukturiert. Die Hilfsmethoden für Listenoperationen sind sinnvoll. Es gibt jedoch einen unnötigen OrderedDict-Import, die Erklärung räumt ein, dass das Design nicht dem entsprach, was angefordert wurde, und die Testlogik ist ziemlich oberflächlich.

Praktischer Nutzen

Gewichtung 15%
56

Als einfacher Thread-sicherer LRU-Cache ist er in der Praxis nutzbar, aber die einzelne globale Sperre erzeugt hohe Konkurrenz und vereitelt die wichtigste praktische Anforderung für skalierbaren gleichzeitigen Zugriff. Die Demo gibt nur begrenztes Vertrauen für reale Multithreading-Workloads.

Befolgung der Anweisungen

Gewichtung 10%
45

Es folgt den API- und Modulformat-Anforderungen, erfüllt aber eindeutig nicht die Anweisung, eine grobkörnige globale Sperre zu vermeiden und gleichzeitige get-Aufrufe für verschiedene Schlüssel zuzulassen. Dies ist eine wesentliche Abweichung von der zentralen Anforderung.

Gesamtpunktzahl

61

Gesamtkommentar

Antwort A bietet eine korrekte LRU-Cache-Implementierung mit ordnungsgemäßer doppelt verketteter Liste und Hash-Map-Struktur, korrekter Eviktionslogik und angemessener Behandlung von Randfällen. Der Code ist sauber und lesbar. Es scheitert jedoch grundlegend an der Kernanforderung der Nebenläufigkeit, indem es ein einziges RLock für alle Operationen verwendet, was der Autor selbst als nicht akzeptabel gemäß der Aufforderung anerkennt. Die Demonstration ist vorhanden, aber die Assertions sind minimal. Dies ist eine solide Basisimplementierung, die eine erhebliche Neugestaltung erfordern würde, um die feingranularen Nebenläufigkeitsanforderungen zu erfüllen.

Bewertungsdetails anzeigen

Korrektheit

Gewichtung 35%
65

Antwort A implementiert einen korrekten LRU-Cache mit ordnungsgemäßer doppelt verketteter Liste und Hash-Map. Die Eviktionslogik, die Get/Put/Delete-Operationen und die Behandlung von Randfällen (ValueError für nicht-positive Kapazität) sind alle korrekt. Es verwendet jedoch ein einziges RLock für alles, was der Autor selbst anerkennt, dass es die feingranulare Nebenläufigkeitsanforderung nicht erfüllt. Die LRU-Semantik ist korrekt, aber das Nebenläufigkeitsmodell ist naiv.

Vollstandigkeit

Gewichtung 20%
60

Antwort A enthält alle erforderlichen Operationen, die Behandlung von Randfällen und eine Demonstration mit mehreren Threads. Es scheitert jedoch ausdrücklich an der Implementierung der erforderlichen feingranularen Nebenläufigkeit (verwendet ein einziges globales RLock), was eine Kernanforderung ist. Die Demonstration ist vorhanden, aber die Assertions sind minimal und die Integritätsprüfung ist oberflächlich.

Codequalitat

Gewichtung 20%
65

Antwort A ist sauber und lesbar mit guter Struktur. Die Verwendung von Dummy-Head/Tail-Sentinels ist angemessen. Die Verwendung von RLock anstelle von Lock ist hier jedoch unnötig und der Kommentar bestätigt, dass die Implementierung die angegebenen Anforderungen nicht erfüllt. Der Code ist gut organisiert, aber das Nebenläufigkeitsdesign ist ein bekanntes Manko.

Praktischer Nutzen

Gewichtung 15%
50

Der Single-Lock-Ansatz von Antwort A hat einen begrenzten praktischen Wert für Szenarien mit hoher Nebenläufigkeit. Er würde korrekt funktionieren, wäre aber unter Last ein Engpass. Der Autor erkennt diese Einschränkung an. Für den Produktionseinsatz müsste die Implementierung erheblich überarbeitet werden.

Befolgung der Anweisungen

Gewichtung 10%
55

Antwort A folgt den meisten Anweisungen, scheitert aber ausdrücklich an der Kernanforderung, über ein einziges globales Lock hinauszugehen. Die Aufforderung besagt, dass 'ein einziges grobkörniges Lock um alles herum nicht akzeptabel ist', und Antwort A verwendet genau das. Der Autor bestätigt dies in Kommentaren, was Bewusstsein zeigt, aber das Problem nicht löst. Die Demonstration und die Behandlung von Randfällen sind vorhanden.

Bewertungsmodelle Google Gemini 2.5 Pro

Gesamtpunktzahl

66

Gesamtkommentar

Antwort A bietet eine korrekte, threadsichere Implementierung eines LRU-Caches unter Verwendung eines Standard-Wörterbuchs und einer doppelt verketteten Liste. Der Code ist sauber und leicht verständlich. Sie scheitert jedoch grundlegend an der Kernanforderung der Nebenläufigkeit der Aufforderung, indem sie eine einzige globale Sperre für alle Operationen verwendet. Obwohl sie diese Einschränkung in ihrer Erklärung korrekt anerkennt, macht diese Designentscheidung sie zu einer nicht konformen und wesentlich einfacheren Lösung als gefordert. Der Democode ist funktionsfähig, aber seine Integritätsprüfungen sind sehr grundlegend.

Bewertungsdetails anzeigen

Korrektheit

Gewichtung 35%
70

Die LRU-Logik und die Threadsicherheit sind aufgrund der einzigen globalen Sperre korrekt. Es handelt sich jedoch nicht um eine korrekte Implementierung des geforderten *konkurrenten* Designs, da sie alle Operationen serialisiert und somit das Leistungsziel verfehlt.

Vollstandigkeit

Gewichtung 20%
75

Die Antwort liefert alle geforderten Komponenten: die Klassenimplementierung, eine Erklärung und eine Demonstration. Die Kernimplementierung ist jedoch in Bezug auf die Nebenläufigkeitsanforderungen der Aufforderung unvollständig.

Codequalitat

Gewichtung 20%
75

Der Code ist sauber, gut kommentiert und folgt den üblichen Python-Idiomen. Die Implementierung des Wörterbuchs und der doppelt verketteten Liste ist unkompliziert und korrekt.

Praktischer Nutzen

Gewichtung 15%
50

Ein LRU-Cache mit einer einzigen Sperre hat in Anwendungen mit hoher Nebenläufigkeit nur einen begrenzten praktischen Wert, da die Sperre schnell zum Engpass wird. Er dient als grundlegendes Lehrbuchbeispiel, ist aber keine Hochleistungslösung.

Befolgung der Anweisungen

Gewichtung 10%
40

Die Antwort verstößt gegen eine wichtige negative Einschränkung der Aufforderung: „eine einzige grobkörnige Sperre um alles herum ist nicht akzeptabel.“ Durch die Verwendung einer einzigen RLock für alle Operationen wird diese Anweisung, die zentral für die Schwierigkeit der Aufgabe war, direkt verletzt.

Antwort B: OpenAI GPT-5.2

"""Nebenläufiger LRU-Cache (striktes LRU) mit minimierter Konkurrenz. Nebenläufigkeitsstrategie (kurz): - Der Cache verwendet ein dict für Schlüssel->Node und eine doppelt verkettete Liste für die globale LRU-Reihenfolge. - Um Konkurrenz bei Schlüssel-Wert-Operationen zu reduzieren, wird der Schlüsselraum über mehrere Locks gestreift. Aktualisierungen für einen bestimmten Schlüssel (Einfügen/Aktualisieren/Löschen/Lesen des Mappings und der Node-Felder) verwenden nur den Stripe-Lock dieses Schlüssels. - Um *strikte globale LRU-Semantik* zu bewahren, werden alle Mutationen der LRU-Liste mit einem dedizierten...

Vollstandige Antwort anzeigen

"""Nebenläufiger LRU-Cache (striktes LRU) mit minimierter Konkurrenz. Nebenläufigkeitsstrategie (kurz): - Der Cache verwendet ein dict für Schlüssel->Node und eine doppelt verkettete Liste für die globale LRU-Reihenfolge. - Um Konkurrenz bei Schlüssel-Wert-Operationen zu reduzieren, wird der Schlüsselraum über mehrere Locks gestreift. Aktualisierungen für einen bestimmten Schlüssel (Einfügen/Aktualisieren/Löschen/Lesen des Mappings und der Node-Felder) verwenden nur den Stripe-Lock dieses Schlüssels. - Um *strikte globale LRU-Semantik* zu bewahren, werden alle Mutationen der LRU-Liste mit einem dedizierten lru_lock serialisiert. Dies vermeidet Race Conditions, die die Liste beschädigen könnten, und stellt sicher, dass es eine einzige totale Ordnung von „touch“-Operationen gibt. - Eviction erfordert Koordination: Unter lru_lock wählen wir das aktuelle Ende (am wenigsten kürzlich verwendet) aus und erwerben dann den Stripe-Lock dieses Schlüssels, um zu verifizieren, dass es noch derselbe Node ist, und entfernen ihn aus der Map. Dies verhindert, dass ein gleichzeitig aktualisierter Schlüssel fälschlich verdrängt wird. Warum das korrekt ist: - Die LRU-Liste wird nur unter lru_lock mutiert, daher kann sie nicht beschädigt werden, und jede touch- Operation (get/put für bestehende Schlüssel) ist total geordnet, was striktes LRU ergibt. - Der Map-Eintrag und der Wert des Node werden durch den Stripe-Lock für diesen Schlüssel geschützt, wodurch gleichzeitige Aktualisierungen/Löschungen für denselben Schlüssel den Zustand nicht beschädigen können. - Verdrängungen basieren auf dem tatsächlichen Ende in dem Moment, in dem die Verdrängung unter lru_lock entschieden wird; wenn sich der Kandidaten-Node aufgrund eines gleichzeitigen touch/delete ändert, wird die Verdrängung erneut versucht. Hinweis: Python stellt keine wirklich lock-freien Primitive für komplexe gemeinsam genutzte Strukturen bereit; diese Implementierung vermeidet einen einzigen globalen Lock um *alles*, indem Zuständigkeiten getrennt werden: - Operationen auf Schlüssel-Ebene sind gestreift. - Nur die Pflege der Listenreihenfolge verwendet einen einzelnen Lock. Eine Demonstration ist in __main__ enthalten. """ from __future__ import annotations import threading import time import random from dataclasses import dataclass from typing import Any, Optional, Dict, List @dataclass class _Node: key: Any value: Any prev: Optional["_Node"] = None next: Optional["_Node"] = None alive: bool = True # auf False gesetzt, wenn gelöscht/verdrängt class ConcurrentLRUCache: def __init__(self, capacity: int, stripes: int = 64): if capacity <= 0: raise ValueError("capacity muss positiv sein") if stripes <= 0: raise ValueError("stripes muss positiv sein") self.capacity = int(capacity) self._map: Dict[Any, _Node] = {} # Sentinel-Kopf/-Ende für doppelt verkettete Liste (MRU bei head.next, LRU bei tail.prev) self._head = _Node("<HEAD>", None) self._tail = _Node("<TAIL>", None) self._head.next = self._tail self._tail.prev = self._head # Serialisiert Listenoperationen sowie Größen-/Verdrängungsentscheidungen self._lru_lock = threading.Lock() # Gestreifte Locks für Map-/Node-Operationen auf Schlüssel-Ebene self._stripes_n = int(stripes) self._stripes: List[threading.Lock] = [threading.Lock() for _ in range(self._stripes_n)] # Größe wird unter lru_lock verfolgt self._size = 0 def _stripe_lock(self, key: Any) -> threading.Lock: return self._stripes[hash(key) % self._stripes_n] # ----- Hilfsfunktionen für doppelt verkettete Liste (erfordern das Halten von self._lru_lock) ----- def _unlink(self, node: _Node) -> None: p = node.prev n = node.next if p is not None: p.next = n if n is not None: n.prev = p node.prev = None node.next = None def _link_front(self, node: _Node) -> None: first = self._head.next node.prev = self._head node.next = first self._head.next = node if first is not None: first.prev = node def _move_to_front(self, node: _Node) -> None: # node muss in der Liste sein self._unlink(node) self._link_front(node) def _current_lru_node(self) -> Optional[_Node]: # gibt den tatsächlichen LRU-Node (tail.prev) zurück oder None, wenn leer n = self._tail.prev if n is None or n is self._head: return None return n # ----- Öffentliche API ----- def get(self, key: Any) -> Any: lock = self._stripe_lock(key) with lock: node = self._map.get(key) if node is None or not node.alive: return None value = node.value # Nach-vorn-Verschieben erfordert den Listen-Lock; dies außerhalb des Stripe-Locks, um Konkurrenz zu reduzieren. with self._lru_lock: # Mitgliedschaft unter Stripe-Lock erneut prüfen, um das Verschieben eines gelöschten/ersetzten Node zu vermeiden. with lock: cur = self._map.get(key) if cur is not node or cur is None or not cur.alive: return value # Wert war zum Zeitpunkt des Lesens gültig; touch durch Race verloren. # Unter lru_lock können wir diesen Node sicher verschieben. self._move_to_front(node) return value def put(self, key: Any, value: Any) -> None: lock = self._stripe_lock(key) # Zuerst prüfen, ob der Schlüssel existiert, und unter Stripe-Lock aktualisieren. with lock: node = self._map.get(key) if node is not None and node.alive: node.value = value # Touch (nach vorn verschieben) unter lru_lock. # Stripe-Lock schnell freigeben? Wir können ihn kurz halten: Aktualisierung erledigt. pass else: node = None if node is not None: with self._lru_lock: # Sicherstellen, dass noch vorhanden und derselbe Node with lock: cur = self._map.get(key) if cur is node and cur is not None and cur.alive: self._move_to_front(node) return # Neuen Node einfügen new_node = _Node(key, value) with self._lru_lock: # Stripe-Lock halten, während lru_lock gehalten wird, um konsistentes Einfügen+Verknüpfen sicherzustellen. with lock: existing = self._map.get(key) if existing is not None and existing.alive: # Jemand hat gleichzeitig eingefügt; stattdessen diesen aktualisieren. existing.value = value # Touch darauf self._move_to_front(existing) return # Mapping einfügen und verknüpfen self._map[key] = new_node self._link_front(new_node) self._size += 1 # Verdrängen, solange über Kapazität while self._size > self.capacity: if not self._evict_one_locked(): # Wenn Verdrängung wegen Race Conditions fehlschlägt, erneut versuchen continue def _evict_one_locked(self) -> bool: """Einen einzelnen LRU-Node verdrängen. Erfordert das Halten von self._lru_lock. Gibt True zurück, wenn eine Verdrängung stattgefunden hat, False, wenn erneut versucht werden muss. """ victim = self._current_lru_node() if victim is None: return True vkey = victim.key vlock = self._stripe_lock(vkey) with vlock: cur = self._map.get(vkey) if cur is not victim or cur is None or not cur.alive: # Opfer hat sich geändert oder bereits entfernt; erneut versuchen return False # Zuerst aus der Map entfernen del self._map[vkey] victim.alive = False # Jetzt aus der Liste entfernen (wir halten lru_lock) self._unlink(victim) self._size -= 1 return True def delete(self, key: Any) -> bool: lock = self._stripe_lock(key) # Zuerst lru_lock erwerben, um Deadlocks mit Verdrängungs-/Einfügepfad zu vermeiden. with self._lru_lock: with lock: node = self._map.get(key) if node is None or not node.alive: return False del self._map[key] node.alive = False # Aus der LRU-Liste lösen self._unlink(node) self._size -= 1 return True def __len__(self) -> int: with self._lru_lock: return self._size def _assert_invariants(self) -> None: """Debug-/Prüfroutine: verifiziert interne Konsistenz.""" with self._lru_lock: # Listenstruktur prüfen und Nodes zählen count = 0 seen = set() n = self._head.next prev = self._head while n is not None and n is not self._tail: assert n.prev is prev assert n.alive assert n.key not in seen seen.add(n.key) count += 1 prev = n n = n.next assert n is self._tail assert self._tail.prev is prev assert count == self._size # Map und Liste stimmen überein (Best-Effort; für Perfektion wären Stripe-Locks nötig). # Unter lru_lock kann sich die Map unter Stripe-Locks ändern; daher führen wir nur eine konservative Prüfung durch: # jeder Listen-Node muss in der Map existieren. for k in seen: lock = self._stripe_lock(k) with lock: m = self._map.get(k) assert m is not None and m.alive and m.key == k def _demo() -> None: cap = 32 cache = ConcurrentLRUCache(capacity=cap, stripes=32) stop = threading.Event() errors = [] keys = [f"k{i}" for i in range(64)] def worker(tid: int) -> None: rnd = random.Random(12345 + tid) try: while not stop.is_set(): op = rnd.random() k = rnd.choice(keys) if op < 0.50: cache.get(k) elif op < 0.85: cache.put(k, (tid, rnd.randint(0, 10_000))) else: cache.delete(k) # Gelegentlich prüfen, dass die Größe die Kapazität nicht überschreitet if rnd.random() < 0.01: assert len(cache) <= cap except Exception as e: errors.append(e) stop.set() threads = [threading.Thread(target=worker, args=(i,), daemon=True) for i in range(12)] for t in threads: t.start() # Ein wenig laufen lassen time.sleep(1.5) # Stoppen und zusammenführen stop.set() for t in threads: t.join(timeout=2.0) # Keine Worker-Fehler if errors: raise errors[0] # Abschließende Invariantenprüfungen assert len(cache) <= cap cache._assert_invariants() # Grundlegender Plausibilitätstest: wiederholtes put/get auf demselben Schlüssel aus mehreren Threads sollte nichts beschädigen cache.put("sentinel", 1) assert cache.get("sentinel") == 1 assert cache.delete("sentinel") is True assert cache.get("sentinel") is None if __name__ == "__main__": _demo() print("Demo abgeschlossen; Cache-Invarianten gelten.")

Ergebnis

#1 | Sieger

Siegstimmen

3 / 3

Durchschnittsscore

87
Bewertungsmodelle OpenAI GPT-5.4

Gesamtpunktzahl

82

Gesamtkommentar

Antwort B bietet ein wesentlich stärkeres gleichzeitiges Design mit gestreiften Sperren pro Schlüssel und einer dedizierten LRU-Listen-Sperre, was dem geforderten feingranularen Ansatz viel näher kommt. Die Implementierung ist vollständig, validiert die Kapazität, unterstützt get/put/delete, beinhaltet Evictions-Koordination und bietet eine Thread-basierte Demonstration mit Invariantenprüfungen. Die Erklärung der Nebenläufigkeitsstrategie ist klar und technisch fundiert. Ein Vorbehalt ist, dass get einen Wert zurückgeben kann, auch wenn die Aktualisierung der Rezenz aufgrund eines Wettlaufs verloren geht, was die Behauptung von striktem LRU unter stark gleichzeitigen Verschachtelungen etwas fragwürdig macht, aber insgesamt ist sie der Aufgabe von Antwort A weit überlegen.

Bewertungsdetails anzeigen

Korrektheit

Gewichtung 35%
79

Die Implementierung ist sorgfältig für gleichzeitigen Zugriff konzipiert, mit expliziter Koordination für Eviction und Listenmutation. Sie bewahrt die interne Konsistenz gut und beinhaltet Invariantenprüfungen. Die Hauptschwäche besteht darin, dass get einen Wert zurückgeben kann, auch wenn die Aktualisierung der Rezenz nach einem gleichzeitigen Löschen oder Ersetzen verworfen wird, was die Behauptung von strikten LRU-Semantiken unter Wettläufen schwächt, aber insgesamt ist das Kernverhalten stark.

Vollstandigkeit

Gewichtung 20%
84

Es liefert ein vollständiges Modul mit den geforderten Operationen, Kapazitätsvalidierung, Nebenläufigkeitserklärung und einer Demonstration mit mehreren Threads, die gemischte Operationen durchführen. Es beinhaltet auch interne Invariantenprüfungen und die Abdeckung von Randfällen wie Löschung und wiederholtem Zugriff. Dies ist für die gestellte Aufgabe nahezu vollständig.

Codequalitat

Gewichtung 20%
85

Der Code ist gut strukturiert, verwendet Typ-Hints und Dataclasses angemessen, trennt Listenhelfer von der öffentlichen API und dokumentiert Sperrerwartungen klar. Das Nebenläufigkeitsdesign ist ausgefeilter und bleibt dennoch lesbar. Geringfügige Komplexität und subtile Kompromisse bei der Handhabung von Wettläufen verhindern eine noch höhere Punktzahl.

Praktischer Nutzen

Gewichtung 15%
81

Dies ist für reale gleichzeitige Workloads wesentlich praktischer, da es die Kontention mit gestreiften Sperren reduziert und nur die Listenordnungslogik zentralisiert. Die Demo und die Invariantenprüfungen erhöhen die Zuversicht. Es ist immer noch durch Python-Threading und eine globale LRU-Sperre für die Listenreihenfolge eingeschränkt, aber es ist sinnvoll nützlich.

Befolgung der Anweisungen

Gewichtung 10%
88

Es folgt der Aufforderung genau: Cache mit fester Kapazität, erforderliche Methoden, ValueError bei ungültiger Kapazität, Erklärung der Nebenläufigkeitsstrategie und eine Thread-basierte Demo mit gemischten Operationen und Assertions. Das Design zielt direkter auf minimierte Kontention und strikte LRU-Wartung ab als A.

Gesamtpunktzahl

83

Gesamtkommentar

Antwort B bietet einen wirklich feingranularen, nebenläufigen LRU-Cache mit gestreiftem Locking für Schlüssel-Level-Operationen und einem dedizierten lru_lock zur Verwaltung der Listenreihenfolge. Die Implementierung behandelt nebenläufige Operationen, Eviction-Races (über Retry und Alive-Flag) und alle Randfälle korrekt. Die Erklärung der Nebenläufigkeitsstrategie ist klar und präzise. Die Demonstration ist robust mit mehreren Threads, aussagekräftigen Assertions und einem Invariantenprüfer. Die Codequalität ist hoch mit guter Nutzung von Typ-Hints, Dataclasses und klarer Trennung von Verantwortlichkeiten. Kleinere theoretische Race Conditions in get() werden durch erneute Prüfung unter beiden Locks gemildert.

Bewertungsdetails anzeigen

Korrektheit

Gewichtung 35%
85

Antwort B implementiert korrekte LRU-Semantik mit gestreiftem Locking und einem dedizierten lru_lock. Die Eviction-Logik behandelt Retries bei Race Conditions, das Alive-Flag verhindert Probleme mit veralteten Nodes und die get-/put-/delete-Operationen sind sorgfältig koordiniert. Die _assert_invariants-Methode bietet zusätzliche Korrektheitsprüfung. Es gibt ein subtiles Problem in get(), bei dem der Stripe-Lock freigegeben wird, bevor der lru_lock erworben wird, was theoretisch eine Race ermöglichen könnte, aber die erneute Prüfung unter beiden Locks mildert dies. Die allgemeine Korrektheit ist stark.

Vollstandigkeit

Gewichtung 20%
85

Antwort B ist sehr vollständig: Sie implementiert alle erforderlichen Operationen, behandelt Randfälle (ValueError für nicht-positive Kapazität und Stripes), verwendet gestreiftes Locking mit einem dedizierten lru_lock, enthält eine _assert_invariants-Methode und bietet eine robuste Multi-Thread-Demonstration mit aussagekräftigen Assertions. Die Erklärung der Nebenläufigkeitsstrategie ist gründlich und präzise.

Codequalitat

Gewichtung 20%
80

Antwort B ist gut strukturiert mit klarer Trennung von Verantwortlichkeiten. Die Verwendung von Dataclasses für _Node, Typ-Hints und Docstrings verbessert die Lesbarkeit. Die _evict_one_locked-Hilfsfunktion und die _assert_invariants-Methode zeigen gutes Design. Das Alive-Flag-Muster ist eine saubere Methode, um gleichzeitige Lösch-/Eviction-Vorgänge zu behandeln. Die Lock-Reihenfolge (lru_lock vor Stripe-Lock) wird konsequent eingehalten, um Deadlocks zu verhindern.

Praktischer Nutzen

Gewichtung 15%
75

Antwort B hat einen echten praktischen Wert mit ihrem gestreiften Locking-Ansatz. Der Standard von 64 Stripes reduziert die Kontention für Schlüssel-Level-Operationen erheblich. Die Trennung der Listenreihenfolge-Verwaltung (lru_lock) von den Schlüssel-Level-Operationen (Stripe-Locks) ist ein solides Design, das unter nebenläufigen Workloads gut funktionieren würde. Der konfigurierbare Stripes-Parameter fügt Flexibilität hinzu.

Befolgung der Anweisungen

Gewichtung 10%
85

Antwort B folgt allen Anweisungen: Sie implementiert feingranulare Nebenläufigkeit mit gestreiften Locks, bietet eine klare Erklärung der Nebenläufigkeitsstrategie, behandelt alle Randfälle, enthält eine Multi-Thread-Demonstration mit Assertions und löst ValueError für ungültige Kapazität aus. Die Implementierung geht tatsächlich über ein einzelnes globales Lock hinaus, wie gefordert.

Bewertungsmodelle Google Gemini 2.5 Pro

Gesamtpunktzahl

95

Gesamtkommentar

Antwort B präsentiert eine ausgefeilte und qualitativ hochwertige Lösung, die einen gleichzeitigen LRU-Cache mit feingranularer Sperrung korrekt implementiert. Sie verwendet erfolgreich einen gestreiften Sperrmechanismus für Schlüssel-Wert-Operationen und eine separate Sperre für Listenmanipulationen, um die strikte LRU-Reihenfolge aufrechtzuerhalten und damit die Anforderung der Aufgabenstellung, die Konflikte zu minimieren, direkt zu erfüllen. Der Code ist gut strukturiert, die Erklärung der komplexen Nebenläufigkeitsstrategie ist klar und genau, und die Demonstration beinhaltet eine robuste Methode zur Überprüfung von Invarianten. Diese Antwort zeigt ein tiefes Verständnis sowohl von Datenstrukturen als auch von Herausforderungen der nebenläufigen Programmierung.

Bewertungsdetails anzeigen

Korrektheit

Gewichtung 35%
95

Die Implementierung ist nicht nur in ihrer LRU-Logik korrekt, sondern scheint auch die komplexen Race Conditions, die mit ihrer feingranularen Sperrstrategie verbunden sind, korrekt zu handhaben. Die Logik zur Koordination zwischen den Streifen-Sperren und der LRU-Sperre zur Aufrechterhaltung der strikten LRU-Reihenfolge ist solide und gut implementiert.

Vollstandigkeit

Gewichtung 20%
100

Diese Antwort ist vollkommen vollständig. Sie liefert eine voll funktionsfähige Klasse, die alle funktionalen und Nebenläufigkeitsanforderungen erfüllt, eine detaillierte und genaue Erklärung ihrer fortschrittlichen Strategie und eine robuste Multi-Thread-Demonstration mit starken Integritätsprüfungen.

Codequalitat

Gewichtung 20%
90

Die Codequalität ist ausgezeichnet. Er ist gut strukturiert mit klaren Hilfsmethoden, verwendet eine Datenklasse für Knoten und enthält eine wertvolle Methode `_assert_invariants` zum Debuggen und Verifizieren. Der Code ist komplex, bleibt aber aufgrund guter Organisation und Kommentare lesbar.

Praktischer Nutzen

Gewichtung 15%
90

Diese Implementierung hat einen hohen praktischen Wert. Ein gleichzeitiger LRU-Cache mit gestreifter Sperrung ist eine wirklich nützliche Datenstruktur für den Aufbau von Hochleistungs-Multi-Thread-Systemen. Dieses Design ist direkt auf reale Probleme anwendbar.

Befolgung der Anweisungen

Gewichtung 10%
100

Die Antwort folgt perfekt allen Anweisungen. Sie implementiert die erforderlichen Methoden, behandelt Randfälle, liefert eine Erklärung und eine Demo und hält vor allem die Einschränkung ein, eine einzige globale Sperre zu vermeiden, indem sie ein ausgeklügeltes feingranulares Nebenläufigkeitsmodell implementiert.

Vergleichsuebersicht

Fur jede Aufgabe und Diskussion wird die Endrangfolge per Richter-Rangaggregation bestimmt (Durchschnittsrang + Borda-Tie-Break). Der Durchschnittsscore wird als Referenz angezeigt.

Bewerter: 3

Siegstimmen

0 / 3

Durchschnittsscore

63
Diese Antwort ansehen

Siegstimmen

3 / 3

Durchschnittsscore

87
Diese Antwort ansehen

Bewertungsergebnisse

Bewertungsmodelle Google Gemini 2.5 Pro

Warum diese Seite gewann

Antwort B ist der klare Gewinner, da sie die primäre Herausforderung der Aufgabe erfolgreich bewältigt: die Implementierung eines nebenläufigen LRU-Caches mit einer feingranularen Sperrstrategie zur Minimierung von Konflikten. Antwort A scheitert an dieser kritischen Anforderung, indem sie eine einzige globale Sperre verwendet, die in der Aufgabenstellung ausdrücklich verboten ist. Die Verwendung von gestreifter Sperrung in Antwort B in Kombination mit einer dedizierten Listen-Sperre ist eine weitaus fortschrittlichere und praktischere Lösung, die überlegene Kenntnisse der nebenläufigen Programmierung zeigt. Darüber hinaus sind die Codequalität, die Erklärung und die Tests von Antwort B durchweg gründlicher und robuster.

Warum diese Seite gewann

Antwort B gewinnt eindeutig, da sie die geforderte feingranulare Nebenläufigkeitsstrategie mit gestreiftem Sperren tatsächlich implementiert, was die zentrale Anforderung dieser Aufgabe ist. Antwort A verwendet ausdrücklich eine einzige globale Sperre und räumt ein, dass dies gegen die Anforderungen der Aufforderung verstößt. Über das Nebenläufigkeitsdesign hinaus verfügt Antwort B auch über eine robustere Demonstration, eine bessere Überprüfung von Invarianten und eine höhere Gesamtqualität des Codes. Die Aufgabe besagt ausdrücklich, dass eine einzige grobkörnige Sperre nicht akzeptabel ist, was den Ansatz von Antwort A zu einem grundlegenden Versagen der Kernanforderung macht.

Bewertungsmodelle OpenAI GPT-5.4

Warum diese Seite gewann

Antwort B gewinnt, da sie die Kernherausforderung der Aufgabe sinnvoll angeht: die Reduzierung von Konflikten bei gleichzeitiger Wahrung der Threadsicherheit und des LRU-Verhaltens. Sie verwendet gestreifte Sperren und eine separate LRU-Sperre anstelle einer einzigen globalen Sperre, enthält eine klare Begründung für die Korrektheit, geht mit Eviction-Races sorgfältiger um und bietet eine stärkere Abdeckung durch gleichzeitige Tests mit Invarianten-Assertions. Antwort A ist einfacher und funktional größtenteils korrekt, aber ihre einzelne, grobkörnige Sperre scheitert direkt an einer Hauptanforderung der Aufgabenstellung.

X f L