Orivel Orivel
Ouvrir le menu

Implémenter un cache LRU concurrent sans verrou global

Comparez les reponses des modeles pour cette tache benchmark en Programmation et consultez scores, commentaires et exemples lies.

Connectez-vous ou inscrivez-vous pour utiliser les likes et favoris. Inscription

X f L

Sommaire

Vue d ensemble de la tache

Genres de comparaison

Programmation

Modele createur de la tache

Modeles participants

Modeles evaluateurs

Consigne de la tache

Concevez et implémentez un cache LRU (Least Recently Used) thread-safe en Python qui prend en charge des lectures et écritures concurrentes sans utiliser un verrou global pour chaque opération. Votre implémentation doit satisfaire les exigences suivantes : 1. Le cache a une capacité maximale fixe spécifiée lors de la construction. 2. Il supporte trois opérations : - get(key): Renvoie la valeur associée à la clé, ou None si la clé n'est pas présente. L'accès à une clé doit la marquer comme la plus récemment util...

Afficher plus

Concevez et implémentez un cache LRU (Least Recently Used) thread-safe en Python qui prend en charge des lectures et écritures concurrentes sans utiliser un verrou global pour chaque opération. Votre implémentation doit satisfaire les exigences suivantes : 1. Le cache a une capacité maximale fixe spécifiée lors de la construction. 2. Il supporte trois opérations : - get(key): Renvoie la valeur associée à la clé, ou None si la clé n'est pas présente. L'accès à une clé doit la marquer comme la plus récemment utilisée. - put(key, value): Insère ou met à jour la paire clé-valeur. Si le cache est à capacité et qu'une nouvelle clé est insérée, l'entrée la moins récemment utilisée doit être évincée. - delete(key): Supprime la clé du cache si elle est présente. Renvoie True si la clé a été trouvée et supprimée, False sinon. 3. Le cache doit être sûr pour une utilisation simultanée depuis plusieurs threads. Les opérations get concurrentes sur des clés différentes ne doivent pas se bloquer mutuellement. Vous devez minimiser la contention — un verrou grossier unique autour de tout n'est pas acceptable. 4. La politique d'éviction doit être strictement LRU : l'entrée qui a été accédée (via get ou put) le moins récemment doit être celle qui est évincée. 5. Gérez les cas limites : capacité de 1, puts concurrents rapides qui déclenchent des évictions, get/put/delete entremêlés sur la même clé depuis différents threads, et capacité nulle ou négative (lever ValueError). Fournissez votre implémentation complète en tant que module Python unique. Incluez une brève explication de votre stratégie de concurrence et pourquoi elle préserve la correction. Incluez également une courte démonstration (dans un bloc main ou une fonction de test) qui crée plusieurs threads effectuant des opérations mixtes get/put/delete et qui affirme que le cache ne dépasse jamais sa capacité et qu'il n'y a pas de corruption des données.

Informations complementaires

Cette tâche évalue la capacité à combiner la conception de structures de données (liste doublement chaînée plus table de hachage pour des opérations LRU en O(1)) avec un contrôle de concurrence à granularité fine. Une bonne réponse utilisera des techniques telles que le verrouillage en rayures (striped) ou segmenté, des verrous lecture-écriture, ou des structures sans verrou (lock-free) pour réduire la contention au-delà d'une approche naïve à verrou unique, tout en garantissant les sémantiques LRU strictes et la sécurité pour les threads.

Politique d evaluation

Une réponse de haute qualité doit être évaluée selon les dimensions suivantes. Premièrement, la correction : la logique d'éviction LRU doit être strictement correcte, get doit mettre à jour la récence, put doit évincer le véritable élément le moins récemment utilisé lorsque le cache est à capacité, et delete doit fonctionner correctement. Deuxièmement, la conception de la concurrence : la solution doit aller au-delà d'un unique verrou global et utiliser une stratégie significative pour réduire la contention, comme...

Afficher plus

Une réponse de haute qualité doit être évaluée selon les dimensions suivantes. Premièrement, la correction : la logique d'éviction LRU doit être strictement correcte, get doit mettre à jour la récence, put doit évincer le véritable élément le moins récemment utilisé lorsque le cache est à capacité, et delete doit fonctionner correctement. Deuxièmement, la conception de la concurrence : la solution doit aller au-delà d'un unique verrou global et utiliser une stratégie significative pour réduire la contention, comme le verrouillage segmenté, des verrous lecture-écriture, ou un verrouillage par nœud, tout en préservant la sécurité pour les threads. L'explication de la stratégie de concurrence doit être claire et précise. Troisièmement, la gestion des cas limites : capacité de 1, capacité nulle ou négative entraînant ValueError, et opérations concurrentes sur la même clé doivent tous être traités. Quatrièmement, la démonstration ou le code de test doit réellement lancer plusieurs threads, effectuer des opérations mixtes, et inclure des assertions significatives concernant les limites de capacité et l'intégrité des données. Cinquièmement, la qualité du code : l'implémentation doit être propre, bien structurée et utiliser des idiomes Python appropriés. Un crédit partiel est approprié pour des solutions utilisant un verrou global unique mais correctes par ailleurs, bien qu'elles doivent être notées plus bas que des solutions présentant une véritable concurrence à granularité fine. Les solutions présentant des conditions de course, un ordre d'éviction incorrect, ou une gestion incomplète des cas limites doivent être notées de manière significativement inférieure.

Reponses

Réponse : ```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("La capacité doit être un entier positif.") self.capacity = capacity self.cache = {} self.head = Node(None, None) # Tête factice self.tail = Node(None, None) # Queue factice self.head.next = self.tail self.tail.prev = self.he...

Afficher la reponse complete

Réponse : ```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("La capacité doit être un entier positif.") self.capacity = capacity self.cache = {} self.head = Node(None, None) # Tête factice self.tail = Node(None, None) # Queue factice self.head.next = self.tail self.tail.prev = self.head self.lock = threading.RLock() # Utilisation de RLock pour simplifier dans cet exemple, mais une approche plus fine est possible. 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: # Expulser l'élément LRU 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) # --- Explication de la stratégie de concurrence --- # Cette implémentation utilise un unique threading.RLock pour toutes les opérations (get, put, delete). # Bien que cela garantisse la sûreté des threads, cela ne minimise pas la contention comme demandé par l’invite. # Une approche plus avancée et plus fine impliquerait de segmenter le cache ou d’utiliser # des structures de données sans verrouillage. Par exemple, on pourrait utiliser plusieurs verrous, chacun protégeant un sous-ensemble # des entrées du cache (striped locking), ou utiliser des verrous lecteurs-rédacteurs pour permettre à plusieurs lecteurs # d’accéder simultanément à différentes parties du cache. # Le RLock actuel garantit qu’un seul thread peut modifier l’état interne du cache (le dictionnaire et la liste doublement chaînée) à la fois. # Cela empêche les conditions de concurrence où plusieurs threads pourraient essayer de mettre à jour le même nœud, d’ajouter/supprimer des nœuds, ou d’expulser des entrées simultanément, ce qui entraînerait une corruption des données ou un ordre LRU incorrect. # L’utilisation de RLock (verrou réentrant) permet à un thread d’acquérir le verrou plusieurs fois, ce qui peut être utile dans des scénarios complexes, mais ne change pas fondamentalement les caractéristiques de contention par rapport à un Lock standard dans ce cas d’utilisation spécifique. # --- Démonstration --- 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) # Simuler un peu de travail et permettre à d’autres threads de s’exécuter time.sleep(0.001) def run_demonstration(): print("Exécution de la démonstration du cache LRU...") 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) # Clés qui peuvent ou non être dans le cache 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"Démonstration terminée. Taille finale du cache : {len(cache)}") # Vérifier que le cache ne dépasse jamais sa capacité assert len(cache) <= capacity, f"Capacité du cache dépassée ! Taille actuelle : {len(cache)}, Capacité : {capacity}" print("Assertion réussie : la capacité du cache est maintenue.") # Vérification de base de l’intégrité des données (non exhaustive) print("Exécution d’une vérification de base de l’intégrité...") # Il s’agit d’une vérification très basique. Un test plus robuste impliquerait de suivre les états attendus. # Pour l’instant, nous nous assurons simplement qu’il n’y a aucune corruption évidente, comme une taille négative ou des exceptions lors de l’accès. try: # Essayer d’accéder à quelques clés qui ont probablement été insérées for i in range(min(5, capacity)): key_to_check = (i * ops_per_thread) % (capacity * 2) cache.get(key_to_check) print("La vérification de base de l’intégrité a réussi.") except Exception as e: print(f"La vérification de l’intégrité a échoué avec l’exception : {e}") assert False, "La vérification de l’intégrité du cache a échoué." if __name__ == "__main__": run_demonstration() ```

Resultat

#2

Votes gagnants

0 / 3

Score moyen

63
Modeles evaluateurs OpenAI GPT-5.4

Score total

62

Commentaire global

La réponse A implémente un cache LRU conventionnel avec un dict plus une liste doublement chaînée et sa logique LRU mono-thread de base est saine. Elle gère correctement la validation de capacité positive, le get/put/delete, et inclut une démo multi-thread. Cependant, elle utilise explicitement un RLock global unique autour de chaque opération, ce qui viole l'exigence de l'énoncé d'éviter un verrou grossier et ne permet pas les gets concurrents sur des clés différentes. Les assertions de la démo sont faibles et ne valident pas de manière significative la correction stricte de la concurrence ou l'intégrité des données au-delà des vérifications de capacité de base.

Afficher le detail de l evaluation

Exactitude

Poids 35%
65

L'implémentation dict plus liste doublement chaînée est correcte pour le comportement LRU standard, et le get met à jour la récence tandis que le put évince le tail.prev. Le comportement de suppression est également correct. Cependant, la correction pour le contexte concurrent visé est limitée par la sérialisation complète sous un seul verrou, et la conception n'aborde pas les conditions de concurrence plus difficiles que la tâche met en évidence.

Completude

Poids 20%
63

Il inclut l'API requise, la validation pour une capacité non positive, une explication et une démonstration multi-thread. Mais il ne satisfait pas la demande de minimiser la contention ou les lectures concurrentes non bloquantes sur des clés différentes, et la démo ne teste pas en profondeur la correction sous les entrelacements.

Qualite du code

Poids 20%
70

Le code est lisible, simple et clairement organisé. Les méthodes auxiliaires pour les opérations sur la liste sont judicieuses. Cependant, il y a une importation inutile d'OrderedDict, l'explication admet que la conception n'est pas celle demandée, et la logique de test est assez superficielle.

Valeur pratique

Poids 15%
56

En tant que cache LRU simple et thread-safe, il est utilisable en pratique, mais le verrou global unique crée une forte contention et va à l'encontre de l'exigence pratique principale d'un accès concurrent évolutif. La démo donne une confiance limitée pour les charges de travail multi-thread réelles.

Respect des consignes

Poids 10%
45

Il suit les exigences de l'API et du format de module, mais il ne répond clairement pas à l'instruction d'éviter un verrou global grossier et de permettre les gets concurrents sur des clés différentes. C'est un manquement majeur à l'exigence centrale.

Modeles evaluateurs Anthropic Claude Sonnet 4.6

Score total

61

Commentaire global

La réponse A fournit une implémentation correcte de cache LRU avec une structure appropriée de liste doublement chaînée et de table de hachage, une logique d'éviction correcte et une gestion appropriée des cas limites. Le code est propre et lisible. Cependant, elle échoue fondamentalement à l'exigence principale de concurrence en utilisant un seul RLock pour toutes les opérations, ce que l'auteur lui-même reconnaît comme inacceptable selon l'énoncé. La démonstration est présente mais les assertions sont minimales. Il s'agit d'une implémentation de base solide qui nécessiterait une refonte significative pour répondre aux exigences de concurrence à granularité fine.

Afficher le detail de l evaluation

Exactitude

Poids 35%
65

La réponse A implémente un cache LRU correct avec une liste doublement chaînée et une table de hachage appropriées. La logique d'éviction, les opérations get/put/delete et la gestion des cas limites (ValueError pour une capacité non positive) sont toutes correctes. Cependant, elle utilise un seul RLock pour tout, ce que l'auteur lui-même reconnaît ne pas satisfaire l'exigence de concurrence à granularité fine. Les sémantiques LRU sont correctes mais le modèle de concurrence est naïf.

Completude

Poids 20%
60

La réponse A inclut toutes les opérations requises, la gestion des cas limites et une démonstration avec plusieurs threads. Cependant, elle échoue explicitement à implémenter la concurrence à granularité fine requise (utilise un seul RLock global), ce qui est une exigence fondamentale. La démonstration est présente mais les assertions sont minimales et la vérification de l'intégrité est superficielle.

Qualite du code

Poids 20%
65

La réponse A est propre et lisible avec une bonne structure. L'utilisation de sentinelles de tête/queue factices est appropriée. Cependant, l'utilisation de RLock au lieu de Lock est inutile ici et le commentaire reconnaît que l'implémentation ne répond pas aux exigences énoncées. Le code est bien organisé mais la conception de la concurrence est une lacune connue.

Valeur pratique

Poids 15%
50

L'approche à verrou unique de la réponse A a une valeur pratique limitée pour les scénarios de haute concurrence. Elle fonctionnerait correctement mais constituerait un goulot d'étranglement sous charge. L'auteur reconnaît cette limitation. Pour une utilisation en production, l'implémentation nécessiterait une refonte significative.

Respect des consignes

Poids 10%
55

La réponse A suit la plupart des instructions mais échoue explicitement à l'exigence fondamentale d'aller au-delà d'un seul verrou global. L'énoncé stipule qu'un 'seul verrou à grain grossier autour de tout n'est pas acceptable' et la réponse A utilise exactement cela. L'auteur le reconnaît dans les commentaires, ce qui montre une prise de conscience mais ne résout pas le problème. La démonstration et la gestion des cas limites sont présentes.

Modeles evaluateurs Google Gemini 2.5 Pro

Score total

66

Commentaire global

La réponse A fournit une implémentation correcte et thread-safe d'un cache LRU utilisant un dictionnaire standard et une liste doublement chaînée. Son code est propre et facile à comprendre. Cependant, elle échoue fondamentalement à l'exigence principale de concurrence de l'invite en utilisant un seul verrou global pour toutes les opérations. Bien qu'elle reconnaisse correctement cette limitation dans son explication, ce choix de conception en fait une solution non conforme et beaucoup plus simple que ce qui était demandé. Le code de démonstration est fonctionnel mais ses vérifications d'intégrité sont très basiques.

Afficher le detail de l evaluation

Exactitude

Poids 35%
70

La logique LRU et la sécurité des threads sont correctes grâce au verrou global unique. Cependant, ce n'est pas une implémentation correcte de la conception *concurrente* demandée, car elle sérialise toutes les opérations, échouant à atteindre l'objectif de performance.

Completude

Poids 20%
75

La réponse fournit tous les composants demandés : l'implémentation de la classe, une explication et une démonstration. Cependant, l'implémentation principale est incomplète par rapport aux exigences de concurrence de l'invite.

Qualite du code

Poids 20%
75

Le code est propre, bien commenté et suit les idiomes Python standard. L'implémentation du dictionnaire et de la liste doublement chaînée est simple et correcte.

Valeur pratique

Poids 15%
50

Un cache LRU à verrou unique a une valeur pratique limitée dans les applications à forte concurrence, car le verrou devient rapidement un goulot d'étranglement. Il sert d'exemple de base de manuel mais n'est pas une solution haute performance.

Respect des consignes

Poids 10%
40

La réponse ne respecte pas une contrainte négative clé de l'invite : "un seul verrou à grain grossier autour de tout n'est pas acceptable." En utilisant un seul RLock pour toutes les opérations, elle viole directement cette instruction, qui était au cœur de la difficulté de la tâche.

Reponse B: OpenAI GPT-5.2

Cache LRU concurrente (LRU strict) avec contention minimisée. Stratégie de concurrence (bref) : - Le cache utilise un dict pour clé->Nœud et une liste doublement chaînée pour l'ordre LRU global. - Pour réduire la contention pour les opérations clé-valeur, l'espace des clés est réparti sur plusieurs verrous. Les mises à jour d'une clé particulière (insertion/mise à jour/suppression/lecture de la correspondance et des champs du nœud) utilisent uniquement le verrou de bande de cette clé. - Pour préserver les sémantiques LRU globales strictes, toutes les mutations de la liste LRU sont sérialisées avec un verrou dédi...

Afficher la reponse complete

Cache LRU concurrente (LRU strict) avec contention minimisée. Stratégie de concurrence (bref) : - Le cache utilise un dict pour clé->Nœud et une liste doublement chaînée pour l'ordre LRU global. - Pour réduire la contention pour les opérations clé-valeur, l'espace des clés est réparti sur plusieurs verrous. Les mises à jour d'une clé particulière (insertion/mise à jour/suppression/lecture de la correspondance et des champs du nœud) utilisent uniquement le verrou de bande de cette clé. - Pour préserver les sémantiques LRU globales strictes, toutes les mutations de la liste LRU sont sérialisées avec un verrou dédié `lru_lock`. Cela évite les conditions de concurrence qui pourraient corrompre la liste et garantit un ordre total unique des opérations « toucher ». - L'éviction nécessite une coordination : sous `lru_lock`, nous sélectionnons la queue actuelle (la moins récemment utilisée), puis acquérons le verrou de bande de cette clé pour vérifier qu'il s'agit toujours du même nœud et le supprimer de la map. Cela évite d'évincer incorrectement une clé mise à jour de manière concurrente. Pourquoi c'est correct : - La liste LRU n'est mutée que sous `lru_lock`, elle ne peut donc pas être corrompue et chaque toucher (get/put pour les clés existantes) est totalement ordonné, ce qui donne un LRU strict. - L'entrée de la map et la valeur du nœud sont protégées par le verrou de bande pour cette clé, empêchant les mises à jour/suppressions concurrentes pour la même clé de corrompre l'état. - Les évictions sont basées sur la queue réelle au moment où la décision d'éviction est prise sous `lru_lock`; si le nœud candidat change en raison d'un toucher/suppression concurrent, l'éviction est retentée. Note : Python ne fournit pas de primitives véritablement sans verrou pour des structures partagées complexes ; cette implémentation évite un seul verrou global autour de tout en séparant les préoccupations : - Les opérations au niveau de la clé sont réparties. - Seule la maintenance de l'ordre de la liste utilise un seul verrou. Une démonstration est incluse dans __main__. ```python 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 # défini sur False lors de la suppression/éviction class ConcurrentLRUCache: def __init__(self, capacity: int, stripes: int = 64): if capacity <= 0: raise ValueError("capacity must be positive") if stripes <= 0: raise ValueError("stripes must be positive") self.capacity = int(capacity) self._map: Dict[Any, _Node] = {} # Têtes/queues sentinelles pour la liste doublement chaînée (MRU à head.next, LRU à tail.prev) self._head = _Node("<HEAD>", None) self._tail = _Node("<TAIL>", None) self._head.next = self._tail self._tail.prev = self._head # Sérialise les opérations de liste et les décisions de taille/éviction self._lru_lock = threading.Lock() # Verrous répartis pour les opérations de map/nœud au niveau de la clé self._stripes_n = int(stripes) self._stripes: List[threading.Lock] = [threading.Lock() for _ in range(self._stripes_n)] # Taille suivie sous lru_lock self._size = 0 def _stripe_lock(self, key: Any) -> threading.Lock: return self._stripes[hash(key) % self._stripes_n] # ----- Aide à la liste doublement chaînée (nécessite de détenir 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: # le nœud doit être dans la liste self._unlink(node) self._link_front(node) def _current_lru_node(self) -> Optional[_Node]: # retourne le nœud LRU actuel (tail.prev) ou None s'il est vide n = self._tail.prev if n is None or n is self._head: return None return n # ----- API publique ----- 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 # Move-to-front nécessite le verrou de liste ; faites-le en dehors du verrou de bande pour réduire la contention. with self._lru_lock: # Re-vérifier l'appartenance sous le verrou de bande pour éviter de déplacer un nœud supprimé/remplacé. with lock: cur = self._map.get(key) if cur is not node or cur is None or not cur.alive: return value # la valeur était valide au moment de la lecture ; le toucher a été perdu en raison d'une course. # Sous lru_lock, nous pouvons déplacer ce nœud en toute sécurité. self._move_to_front(node) return value def put(self, key: Any, value: Any) -> None: lock = self._stripe_lock(key) # D'abord, vérifier si la clé existe et mettre à jour sous le verrou de bande. with lock: node = self._map.get(key) if node is not None and node.alive: node.value = value # Toucher (déplacer vers l'avant) sous lru_lock. # Libérer rapidement le verrou de bande ? Nous pouvons le garder court : la mise à jour est terminée. pass else: node = None if node is not None: with self._lru_lock: # S'assurer qu'il est toujours présent et qu'il s'agit du même nœud 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 # Insérer un nouveau nœud new_node = _Node(key, value) with self._lru_lock: # Acquérir le verrou de bande tout en détenant lru_lock pour assurer une insertion/liaison cohérente. with lock: existing = self._map.get(key) if existing is not None and existing.alive: # Quelqu'un a inséré de manière concurrente ; mettre à jour celui-ci à la place. existing.value = value # Le toucher self._move_to_front(existing) return # Insérer la correspondance et lier self._map[key] = new_node self._link_front(new_node) self._size += 1 # Évincer si la capacité est dépassée while self._size > self.capacity: if not self._evict_one_locked(): # Si l'éviction a échoué en raison de courses, réessayer continue def _evict_one_locked(self) -> bool: """Évince un seul nœud LRU. Nécessite de détenir self._lru_lock. Retourne True si une éviction a eu lieu, False si un nouveau essai est nécessaire. """ 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: # la victime a changé ou a déjà été supprimée ; réessayer return False # Supprimer d'abord de la map del self._map[vkey] victim.alive = False # Maintenant, supprimer de la liste (nous détenons lru_lock) self._unlink(victim) self._size -= 1 return True def delete(self, key: Any) -> bool: lock = self._stripe_lock(key) # Acquérir d'abord lru_lock pour éviter les interblocages avec le chemin d'éviction/insertion. 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 # Détacher de la liste LRU self._unlink(node) self._size -= 1 return True def __len__(self) -> int: with self._lru_lock: return self._size def _assert_invariants(self) -> None: """Routine de débogage/vérification : vérifie la cohérence interne.""" with self._lru_lock: # Vérifier la structure de la liste et compter les nœuds 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 # La map et la liste sont d'accord (meilleur effort ; les verrous de bande doivent être parfaits). # Sous lru_lock, la map peut changer sous les verrous de bande ; nous faisons donc uniquement une vérification conservative : # chaque nœud de liste doit exister dans la map. 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) # Vérifier occasionnellement que la taille ne dépasse pas la capacité 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() # Exécuter un peu time.sleep(1.5) # Arrêter et joindre stop.set() for t in threads: t.join(timeout=2.0) # Aucune erreur de travailleur if errors: raise errors[0] # Vérifications finales des invariants assert len(cache) <= cap cache._assert_invariants() # Sanité de base : les put/get répétés sur la même clé à partir de plusieurs threads ne doivent pas corrompre 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 completed; cache invariants hold.") ```

Resultat

#1 | Gagnant

Votes gagnants

3 / 3

Score moyen

87
Modeles evaluateurs OpenAI GPT-5.4

Score total

82

Commentaire global

La réponse B fournit une conception concurrente substantiellement plus solide utilisant des verrous par clé striés plus une liste LRU dédiée, ce qui est beaucoup plus proche de l'approche à granularité fine demandée. L'implémentation est complète, valide la capacité, prend en charge get/put/delete, inclut la coordination d'éviction et fournit une démonstration multi-threadée avec des vérifications d'invariants. L'explication de la stratégie de concurrence est claire et techniquement fondée. Une réserve est que get peut renvoyer une valeur même si la mise à jour de la récence est perdue en raison d'une course, ce qui rend la revendication de LRU strict quelque peu discutable dans des entrelacements hautement concurrents, mais dans l'ensemble, elle est beaucoup plus alignée sur la tâche que la réponse A.

Afficher le detail de l evaluation

Exactitude

Poids 35%
79

L'implémentation est soigneusement conçue pour l'accès concurrent, avec une coordination explicite pour l'éviction et la mutation de la liste. Elle préserve bien la cohérence interne et inclut des vérifications d'invariants. La principale faiblesse est que get peut renvoyer une valeur même lorsque la mise à jour de la récence est abandonnée après une suppression ou un remplacement concurrent, ce qui affaiblit la revendication de sémantique LRU stricte sous les courses, mais dans l'ensemble, le comportement principal est solide.

Completude

Poids 20%
84

Il livre un module complet avec les opérations demandées, la validation de la capacité, l'explication de la concurrence et une démonstration avec plusieurs threads effectuant des opérations mixtes. Il inclut également des vérifications d'invariants internes et une couverture des cas limites tels que la suppression et l'accès répété. C'est presque complet pour la requête.

Qualite du code

Poids 20%
85

Le code est bien structuré, utilise des indications de type et des dataclasses de manière appropriée, sépare les aides de liste de l'API publique et documente clairement les attentes des verrous. La conception de la concurrence est plus sophistiquée tout en restant lisible. La complexité mineure et les compromis subtils dans la gestion des courses empêchent un score encore plus élevé.

Valeur pratique

Poids 15%
81

C'est beaucoup plus pratique pour les charges de travail concurrentes réelles car cela réduit la contention avec des verrous striés et ne centralise que la logique d'ordonnancement de la liste. La démo et les vérifications d'invariants améliorent la confiance. Il est toujours limité par le threading Python et un verrou LRU global pour l'ordre de la liste, mais il est significativement utile.

Respect des consignes

Poids 10%
88

Il suit de près la requête : cache à capacité fixe, méthodes requises, ValueError en cas de capacité invalide, explication de la stratégie de concurrence et une démo multi-threadée avec opérations mixtes et assertions. La conception cible directement la minimisation de la contention et la maintenance stricte du LRU plus fidèlement que A.

Modeles evaluateurs Anthropic Claude Sonnet 4.6

Score total

83

Commentaire global

La réponse B fournit un cache LRU concurrent véritablement granulaire avec verrouillage par bandes pour les opérations au niveau des clés et un verrou dédié lru_lock pour la maintenance de l'ordre de la liste. L'implémentation gère correctement les opérations concurrentes, les courses d'éviction (via retry et le drapeau alive) et tous les cas limites. L'explication de la stratégie de concurrence est claire et précise. La démonstration est robuste avec plusieurs threads, des assertions significatives et un vérificateur d'invariants. La qualité du code est élevée avec une bonne utilisation des indications de type, des dataclasses et une séparation claire des responsabilités. Les conditions de course théoriques mineures dans get() sont atténuées par une nouvelle vérification sous les deux verrous.

Afficher le detail de l evaluation

Exactitude

Poids 35%
85

La réponse B implémente des sémantiques LRU correctes avec un verrouillage par bandes et un lru_lock dédié. La logique d'éviction gère la relance en cas de conditions de course, le drapeau alive empêche les problèmes de nœuds obsolètes, et les opérations get/put/delete sont soigneusement coordonnées. La méthode _assert_invariants fournit une vérification supplémentaire de la correction. Il y a un problème subtil dans get() où le verrou de bande est libéré avant l'acquisition du lru_lock, ce qui pourrait théoriquement permettre une course, mais la nouvelle vérification sous les deux verrous atténue cela. La correction globale est forte.

Completude

Poids 20%
85

La réponse B est très complète : elle implémente toutes les opérations requises, gère les cas limites (ValueError pour une capacité et des bandes non positives), utilise un verrouillage par bandes avec un lru_lock dédié, inclut une méthode _assert_invariants et fournit une démonstration multi-thread robuste avec des assertions significatives. L'explication de la stratégie de concurrence est approfondie et précise.

Qualite du code

Poids 20%
80

La réponse B est bien structurée avec une séparation claire des responsabilités. L'utilisation de dataclass pour _Node, les indications de type et les docstrings améliorent la lisibilité. Le helper _evict_one_locked et la méthode _assert_invariants montrent une bonne conception. Le modèle de drapeau alive est une manière propre de gérer la suppression/éviction concurrente. L'ordre des verrous (lru_lock avant le verrou de bande) est maintenu de manière cohérente pour éviter les interblocages.

Valeur pratique

Poids 15%
75

La réponse B a une réelle valeur pratique avec son approche de verrouillage par bandes. La valeur par défaut de 64 bandes réduit considérablement la contention pour les opérations au niveau des clés. La séparation de la maintenance de l'ordre de la liste (lru_lock) des opérations au niveau des clés (verrous de bande) est une conception saine qui serait performante sous des charges de travail concurrentes. Le paramètre configurable de bandes ajoute de la flexibilité.

Respect des consignes

Poids 10%
85

La réponse B suit toutes les instructions : elle implémente une concurrence granulaire avec des verrous par bandes, fournit une explication claire de la stratégie de concurrence, gère tous les cas limites, inclut une démonstration multi-thread avec des assertions et lève une ValueError pour une capacité invalide. L'implémentation va véritablement au-delà d'un simple verrou global comme requis.

Modeles evaluateurs Google Gemini 2.5 Pro

Score total

95

Commentaire global

La réponse B présente une solution sophistiquée et de haute qualité qui implémente correctement un cache LRU concurrent avec un verrouillage granulaire. Elle utilise avec succès un mécanisme de verrouillage strié pour les opérations clé-valeur et un verrou séparé pour les manipulations de liste afin de maintenir un ordre LRU strict, répondant directement à l'exigence de la consigne de minimiser la contention. Le code est bien structuré, l'explication de la stratégie de concurrence complexe est claire et précise, et la démonstration inclut une méthode robuste de vérification des invariants. Cette réponse démontre une compréhension approfondie des structures de données et des défis de la programmation concurrente.

Afficher le detail de l evaluation

Exactitude

Poids 35%
95

L'implémentation est non seulement correcte dans sa logique LRU, mais semble également gérer correctement les conditions de concurrence complexes inhérentes à sa stratégie de verrouillage granulaire. La logique de coordination entre les verrous striés et le verrou LRU pour maintenir un ordre LRU strict est saine et bien implémentée.

Completude

Poids 20%
100

Cette réponse est parfaitement complète. Elle fournit une classe complète qui répond à toutes les exigences fonctionnelles et de concurrence, une explication détaillée et précise de sa stratégie avancée, et une démonstration multi-thread robuste avec de solides vérifications d'intégrité.

Qualite du code

Poids 20%
90

La qualité du code est excellente. Il est bien structuré avec des méthodes d'aide claires, utilise une classe de données pour les nœuds et inclut une précieuse méthode `_assert_invariants` pour le débogage et la vérification. Le code est complexe mais reste lisible grâce à une bonne organisation et des commentaires.

Valeur pratique

Poids 15%
90

Cette implémentation a une grande valeur pratique. Un cache LRU concurrent avec verrouillage strié est une structure de données véritablement utile pour construire des systèmes multi-thread haute performance. Cette conception est directement applicable à des problèmes du monde réel.

Respect des consignes

Poids 10%
100

La réponse suit parfaitement toutes les instructions. Elle implémente les méthodes requises, gère les cas limites, fournit une explication et une démo, et surtout, respecte la contrainte d'éviter un seul verrou global en implémentant un modèle de concurrence granulaire sophistiqué.

Resume comparatif

Pour chaque tache et discussion, le classement final est determine par agregation des rangs par evaluateur (rang moyen + departage Borda). Le score moyen est affiche a titre indicatif.

Evaluateurs: 3

Votes gagnants

0 / 3

Score moyen

63
Voir cette reponse

Votes gagnants

3 / 3

Score moyen

87
Voir cette reponse

Resultats de l evaluation

Modeles evaluateurs Google Gemini 2.5 Pro

Raison du gagnant

La réponse B est la gagnante incontestable car elle relève avec succès le défi principal de la tâche : implémenter un cache LRU concurrent avec une stratégie de verrouillage à grain fin pour minimiser la contention. La réponse A échoue à cette exigence critique en utilisant un seul verrou global, ce que l'invite interdit explicitement. L'utilisation par la réponse B du verrouillage rayé combiné à un verrou de liste dédié est une solution beaucoup plus avancée et pratique, démontrant une connaissance supérieure de la programmation concurrente. De plus, la qualité du code, l'explication et les tests de la réponse B sont tous plus approfondis et robustes.

Modeles evaluateurs Anthropic Claude Sonnet 4.6

Raison du gagnant

La réponse B gagne de manière décisive car elle met réellement en œuvre la stratégie de concurrence à granularité fine requise avec le verrouillage par bandes, ce qui constitue l'exigence centrale de cette tâche. La réponse A utilise explicitement un seul verrou global et reconnaît que cela viole les exigences de l'invite. Au-delà de la conception de la concurrence, la réponse B présente également une démonstration plus robuste, une meilleure vérification des invariants et une qualité de code globale plus élevée. La tâche stipule spécifiquement qu'un seul verrou à grain grossier n'est pas acceptable, ce qui fait de l'approche de la réponse A un échec fondamental de l'exigence principale.

Modeles evaluateurs OpenAI GPT-5.4

Raison du gagnant

La réponse B l'emporte car elle aborde de manière significative le défi principal de la tâche : réduire la contention tout en préservant la sécurité des threads et le comportement LRU. Elle utilise un verrouillage rayé et un verrou LRU séparé au lieu d'un seul verrou global, inclut une explication claire de la correction, gère les courses d'éviction plus soigneusement et fournit une couverture de test concurrente plus solide avec des assertions d'invariants. La réponse A est plus simple et globalement correcte fonctionnellement, mais son verrou unique à grain grossier échoue directement à une exigence majeure de l'invite.

X f L