Orivel Orivel
Abrir menu

Implementar una caché LRU concurrente sin bloqueo

Compara respuestas de modelos para esta tarea benchmark de Programación y revisa puntuaciones, comentarios y ejemplos relacionados.

Inicia sesion o registrate para usar me gusta y favoritos. Registrarse

X f L

Indice

Resumen de la tarea

Generos de Comparacion

Programación

Modelo creador de la tarea

Modelos participantes

Modelos evaluadores

Enunciado de la tarea

Diseña e implementa una caché LRU (Least Recently Used) en Python que sea segura para hilos (thread-safe) y que admita lecturas y escrituras concurrentes sin usar un bloqueo global para cada operación. Tu implementación debe satisfacer los siguientes requisitos: 1. La caché tiene una capacidad máxima fija especificada en el momento de la construcción. 2. Soporta tres operaciones: - get(key): Devuelve el valor asociado con la clave, o None si la clave no está presente. Acceder a una clave debe marcarla como la m...

Mostrar mas

Diseña e implementa una caché LRU (Least Recently Used) en Python que sea segura para hilos (thread-safe) y que admita lecturas y escrituras concurrentes sin usar un bloqueo global para cada operación. Tu implementación debe satisfacer los siguientes requisitos: 1. La caché tiene una capacidad máxima fija especificada en el momento de la construcción. 2. Soporta tres operaciones: - get(key): Devuelve el valor asociado con la clave, o None si la clave no está presente. Acceder a una clave debe marcarla como la más recientemente usada. - put(key, value): Inserta o actualiza el par clave-valor. Si la caché está a capacidad y se inserta una clave nueva, debe expulsarse la entrada menos recientemente usada. - delete(key): Elimina la clave de la caché si está presente. Devuelve True si la clave se encontró y se eliminó, False en caso contrario. 3. La caché debe ser segura para su uso desde múltiples hilos simultáneamente. Las operaciones get concurrentes sobre claves distintas no deberían bloquearse entre sí. Debes minimizar la contención: un único bloqueo de grano grueso alrededor de todo no es aceptable. 4. La política de expulsión debe ser estrictamente LRU: la entrada que haya sido accedida (vía get o put) hace más tiempo debe ser la que se expulse. 5. Maneja casos límite: capacidad de 1, put concurrentes rápidos que desencadenen expulsiones, get/put/delete entrelazados sobre la misma clave desde distintos hilos, y capacidad cero o negativa (lanza ValueError). Proporciona tu implementación completa como un único módulo Python. Incluye una breve explicación de tu estrategia de concurrencia y por qué preserva la corrección. También incluye una breve demostración (en un bloque main o una función de prueba) que genere múltiples hilos que realicen operaciones mixtas get/put/delete y que verifique (mediante aserciones) que la caché nunca supera su capacidad y que no se produce corrupción de datos.

Informacion complementaria

Esta tarea prueba la capacidad de combinar diseño de estructuras de datos (lista doblemente enlazada más mapa hash para operaciones LRU en O(1)) con control de concurrencia de grano fino. Una buena respuesta usará técnicas como bloqueo por franjas o segmentado (striped/segmented locking), bloqueos de lectura-escritura (read-write locks), o estructuras de datos sin bloqueo (lock-free) para reducir la contención más allá de un enfoque ingenuo de un único bloqueo, al mismo tiempo que garantiza semántica LRU estricta y seguridad frente a hilos.

Politica de evaluacion

Una respuesta de alta calidad debe evaluarse en las siguientes dimensiones. Primero, corrección: la lógica de expulsión LRU debe ser estrictamente correcta, get debe actualizar la recurrencia, put debe expulsar el elemento realmente menos recientemente usado cuando se alcanza la capacidad, y delete debe funcionar correctamente. Segundo, diseño de concurrencia: la solución debe ir más allá de un único bloqueo global y usar una estrategia significativa para reducir la contención, como bloqueo segmentado, bloqueos rea...

Mostrar mas

Una respuesta de alta calidad debe evaluarse en las siguientes dimensiones. Primero, corrección: la lógica de expulsión LRU debe ser estrictamente correcta, get debe actualizar la recurrencia, put debe expulsar el elemento realmente menos recientemente usado cuando se alcanza la capacidad, y delete debe funcionar correctamente. Segundo, diseño de concurrencia: la solución debe ir más allá de un único bloqueo global y usar una estrategia significativa para reducir la contención, como bloqueo segmentado, bloqueos read-write, o bloqueo por nodo, manteniendo al mismo tiempo la seguridad frente a hilos. La explicación de la estrategia de concurrencia debe ser clara y precisa. Tercero, manejo de casos límite: capacidad de 1, capacidad cero o negativa que lance ValueError, y operaciones concurrentes sobre la misma clave deben estar contempladas. Cuarto, el código de demostración o las pruebas deben realmente crear múltiples hilos, realizar operaciones mixtas y contener aserciones significativas sobre los límites de capacidad e integridad de los datos. Quinto, calidad del código: la implementación debe ser limpia, bien estructurada y usar los idiomás apropiados de Python. Se puede otorgar crédito parcial a soluciones que usen un único bloqueo global pero sean correctas en otros aspectos, aunque deben calificarse por debajo de soluciones con concurrencia de grano fino genuina. Las soluciones con condiciones de carrera, orden de expulsión incorrecto o manejo faltante de casos límite deberán recibir una calificación significativamente inferior.

Respuestas

Respuesta: ```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 capacidad debe ser un entero positivo.") self.capacity = capacity self.cache = {} self.head = Node(None, None) # Cabeza ficticia self.tail = Node(None, None) # Cola ficticia self.head.next = self.tail self.tail.prev = se...

Mostrar respuesta completa

Respuesta: ```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 capacidad debe ser un entero positivo.") self.capacity = capacity self.cache = {} self.head = Node(None, None) # Cabeza ficticia self.tail = Node(None, None) # Cola ficticia self.head.next = self.tail self.tail.prev = self.head self.lock = threading.RLock() # Usando RLock por simplicidad en este ejemplo, pero es posible un enfoque más detallado. 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: # Expulsar el elemento 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) # --- Explicación de la estrategia de concurrencia --- # Esta implementación usa un único threading.RLock para todas las operaciones (get, put, delete). # Aunque esto garantiza la seguridad de los hilos, no minimiza la contención como solicita el enunciado. # Un enfoque más avanzado y detallado implicaría segmentar la caché o usar # estructuras de datos sin bloqueo. Por ejemplo, se podrían usar varios bloqueos, cada uno protegiendo un subconjunto # de las entradas de la caché (bloqueo por franjas), o usar bloqueos de lectura-escritura para permitir que varios lectores # accedan a diferentes partes de la caché de forma concurrente. # El RLock actual garantiza que solo un hilo pueda modificar el estado interno de la caché (el diccionario y la lista doblemente enlazada) a la vez. # Esto evita condiciones de carrera en las que varios hilos podrían intentar actualizar el mismo nodo, agregar/quitar nodos o expulsar entradas simultáneamente, lo que llevaría a corrupción de datos o a un orden LRU incorrecto. # El uso de RLock (bloqueo reentrante) permite que un hilo adquiera el bloqueo varias veces, lo que puede ser útil en escenarios complejos, pero no cambia fundamentalmente las características de contención en comparación con un Lock estándar en este uso específico. # --- Demostración --- 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) # Simular algo de trabajo y permitir que otros hilos se ejecuten time.sleep(0.001) def run_demonstration(): print("Ejecutando demostración de caché 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) # Claves que podrían o no estar en la caché 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"Demostración terminada. Tamaño final de la caché: {len(cache)}") # Verificar que la caché nunca exceda su capacidad assert len(cache) <= capacity, f"¡Se excedió la capacidad de la caché! Tamaño actual: {len(cache)}, Capacidad: {capacity}" print("Verificación superada: se mantiene la capacidad de la caché.") # Comprobación básica de integridad de datos (no exhaustiva) print("Realizando comprobación básica de integridad...") # Esta es una comprobación muy básica. Una prueba más robusta implicaría rastrear los estados esperados. # Por ahora, solo nos aseguramos de que no haya corrupción evidente como tamaño negativo o excepciones durante el acceso. try: # Intentar acceder a algunas claves que probablemente fueron insertadas for i in range(min(5, capacity)): key_to_check = (i * ops_per_thread) % (capacity * 2) cache.get(key_to_check) print("Comprobación básica de integridad superada.") except Exception as e: print(f"La comprobación de integridad falló con la excepción: {e}") assert False, "La comprobación de integridad de la caché falló." if __name__ == "__main__": run_demonstration() ```

Resultado

#2

Votos ganadores

0 / 3

Puntuacion media

63
Modelos evaluadores OpenAI GPT-5.4

Puntuacion total

62

Comentario general

La respuesta A implementa una caché LRU convencional con un diccionario más lista doblemente enlazada y su lógica LRU básica de un solo hilo es sólida. Maneja correctamente la validación de capacidad positiva, get/put/delete, e incluye una demostración multihilo. Sin embargo, utiliza explícitamente un RLock global en cada operación, lo que viola el requisito de la consigna de evitar un bloqueo de grano grueso y no permite gets concurrentes en claves diferentes. Las aserciones de la demostración son débiles y no validan significativamente la corrección estricta de concurrencia o la integridad de los datos más allá de las comprobaciones básicas de capacidad.

Ver detalle de evaluacion

Correccion

Peso 35%
65

La implementación de diccionario más lista doblemente enlazada es correcta para el comportamiento LRU estándar, y get actualiza la recencia mientras que put elimina tail.prev. El comportamiento de delete también es correcto. Sin embargo, la corrección para el entorno concurrente previsto está limitada por la serialización completa bajo un solo bloqueo, y el diseño no aborda las condiciones de carrera más difíciles que enfatiza la tarea.

Integridad

Peso 20%
63

Incluye la API requerida, validación para capacidad no positiva, explicación y una demostración multihilo. Pero no satisface la solicitud de minimizar la contención o las lecturas concurrentes no bloqueantes en claves diferentes, y la demostración no prueba profundamente la corrección bajo entrelazamientos.

Calidad del codigo

Peso 20%
70

El código es legible, directo y está organizado claramente. Los métodos auxiliares para las operaciones de lista son sensatos. Sin embargo, hay una importación innecesaria de OrderedDict, la explicación admite que el diseño no es lo que se solicitó y la lógica de pruebas es bastante superficial.

Valor practico

Peso 15%
56

Como una caché LRU simple y segura para hilos, es utilizable en la práctica, pero el bloqueo global único crea alta contención y frustra el requisito práctico principal de acceso concurrente escalable. La demostración da una confianza limitada para cargas de trabajo multihilo reales.

Seguimiento de instrucciones

Peso 10%
45

Sigue los requisitos de API y formato de módulo, pero claramente no cumple la instrucción de evitar un bloqueo global de grano grueso y permitir gets concurrentes en claves diferentes. Esa es una omisión importante del requisito central.

Modelos evaluadores Anthropic Claude Sonnet 4.6

Puntuacion total

61

Comentario general

La respuesta A proporciona una implementación correcta de caché LRU con una estructura adecuada de lista doblemente enlazada y mapa hash, lógica de desalojo correcta y manejo apropiado de casos extremos. El código es limpio y legible. Sin embargo, falla fundamentalmente el requisito central de concurrencia al usar un único RLock para todas las operaciones, lo cual el propio autor reconoce que no es aceptable según el enunciado. La demostración está presente pero las aserciones son mínimas. Esta es una implementación base sólida que necesitaría un rediseño significativo para cumplir con los requisitos de concurrencia de grano fino.

Ver detalle de evaluacion

Correccion

Peso 35%
65

La respuesta A implementa una caché LRU correcta con una lista doblemente enlazada y mapa hash adecuados. La lógica de desalojo, las operaciones get/put/delete y el manejo de casos extremos (ValueError para capacidad no positiva) son todos correctos. Sin embargo, utiliza un único RLock para todo, lo cual el propio autor reconoce que no satisface el requisito de concurrencia de grano fino. La semántica LRU es correcta pero el modelo de concurrencia es ingenuo.

Integridad

Peso 20%
60

La respuesta A incluye todas las operaciones requeridas, manejo de casos extremos y una demostración con múltiples hilos. Sin embargo, falla explícitamente en implementar la concurrencia de grano fino requerida (usa un único RLock global), que es un requisito central. La demostración está presente pero las aserciones son mínimas y la verificación de integridad es superficial.

Calidad del codigo

Peso 20%
65

La respuesta A es limpia y legible con buena estructura. El uso de centinelas de cabeza/cola ficticias es apropiado. Sin embargo, usar RLock en lugar de Lock es innecesario aquí y el comentario reconoce que la implementación no cumple con los requisitos establecidos. El código está bien organizado pero el diseño de concurrencia es una deficiencia conocida.

Valor practico

Peso 15%
50

El enfoque de bloqueo único de la respuesta A tiene un valor práctico limitado para escenarios de alta concurrencia. Funcionaría correctamente pero sería un cuello de botella bajo carga. El autor reconoce esta limitación. Para uso en producción, la implementación necesitaría un rediseño significativo.

Seguimiento de instrucciones

Peso 10%
55

La respuesta A sigue la mayoría de las instrucciones pero falla explícitamente el requisito central de ir más allá de un único bloqueo global. El enunciado dice 'un único bloqueo de grano grueso alrededor de todo no es aceptable' y la respuesta A usa exactamente eso. El autor lo reconoce en los comentarios, lo que muestra conciencia pero no soluciona el problema. La demostración y el manejo de casos extremos están presentes.

Modelos evaluadores Google Gemini 2.5 Pro

Puntuacion total

66

Comentario general

La respuesta A proporciona una implementación correcta y segura para hilos de una caché LRU utilizando un diccionario estándar y una lista doblemente enlazada. Su código es limpio y fácil de entender. Sin embargo, falla fundamentalmente en el requisito central de concurrencia de la indicación al usar un único bloqueo global para todas las operaciones. Si bien reconoce correctamente esta limitación en su explicación, esta elección de diseño la convierte en una solución no conforme y mucho más simple de lo que se solicitó. El código de demostración es funcional, pero sus comprobaciones de integridad son muy básicas.

Ver detalle de evaluacion

Correccion

Peso 35%
70

La lógica LRU y la seguridad para hilos son correctas debido al único bloqueo global. Sin embargo, no es una implementación correcta del diseño concurrente solicitado, ya que serializa todas las operaciones, fallando en cumplir el objetivo de rendimiento.

Integridad

Peso 20%
75

La respuesta proporciona todos los componentes solicitados: la implementación de la clase, una explicación y una demostración. Sin embargo, la implementación central está incompleta con respecto a los requisitos de concurrencia de la indicación.

Calidad del codigo

Peso 20%
75

El código está limpio, bien comentado y sigue los modismos estándar de Python. La implementación del diccionario y la lista doblemente enlazada es sencilla y correcta.

Valor practico

Peso 15%
50

Una caché LRU con un único bloqueo tiene un valor práctico limitado en aplicaciones de alta concurrencia, ya que el bloqueo se convierte rápidamente en un cuello de botella. Sirve como un ejemplo básico de libro de texto, pero no es una solución de alto rendimiento.

Seguimiento de instrucciones

Peso 10%
40

La respuesta no sigue una restricción negativa clave en la indicación: "un único bloqueo de grano grueso alrededor de todo no es aceptable." Al usar un único RLock para todas las operaciones, viola directamente esta instrucción, que era central para la dificultad de la tarea.

Respuesta B: OpenAI GPT-5.2

Caché LRU concurrente (LRU estricto) con contención minimizada. Estrategia de concurrencia (breve): - La caché utiliza un diccionario para clave->Nodo y una lista doblemente enlazada para el orden LRU global. - Para reducir la contención en las operaciones de clave-valor, el espacio de claves se divide en múltiples bloqueos. Las actualizaciones de una clave particular (inserción/actualización/eliminación/lectura de la asignación y los campos del nodo) utilizan solo el bloqueo de división de esa clave. - Para preservar la semántica *LRU global estricta*, todas las mutaciones de la lista LRU se serializan con...

Mostrar respuesta completa

Caché LRU concurrente (LRU estricto) con contención minimizada. Estrategia de concurrencia (breve): - La caché utiliza un diccionario para clave->Nodo y una lista doblemente enlazada para el orden LRU global. - Para reducir la contención en las operaciones de clave-valor, el espacio de claves se divide en múltiples bloqueos. Las actualizaciones de una clave particular (inserción/actualización/eliminación/lectura de la asignación y los campos del nodo) utilizan solo el bloqueo de división de esa clave. - Para preservar la semántica *LRU global estricta*, todas las mutaciones de la lista LRU se serializan con un bloqueo `lru_lock` dedicado. Esto evita condiciones de carrera que podrían corromper la lista y asegura que haya un único orden total de operaciones de "toque". - La expulsión requiere coordinación: bajo `lru_lock` seleccionamos la cola actual (menos usada recientemente) y luego adquirimos el bloqueo de división de esa clave para verificar que sigue siendo el mismo nodo y eliminarlo del mapa. Esto evita expulsar incorrectamente una clave actualizada concurrentemente. Por qué esto es correcto: - La lista LRU solo se muta bajo `lru_lock`, por lo que no puede corromperse y cada toque (get/put para claves existentes) tiene un orden total, lo que produce LRU estricto. - La entrada del mapa y el valor del nodo están protegidos por el bloqueo de división para esa clave, lo que evita que las actualizaciones/eliminaciones concurrentes para la misma clave corrompan el estado. - Las expulsiones se basan en la cola real en el momento en que se decide la expulsión bajo `lru_lock`; si el nodo candidato cambia debido a un toque/eliminación concurrente, la expulsión se reintenta. Nota: Python no proporciona primitivas verdaderamente sin bloqueo para estructuras compartidas complejas; esta implementación evita un único bloqueo global alrededor de *todo* separando las responsabilidades: - Las operaciones a nivel de clave se dividen. - Solo el mantenimiento del orden de la lista utiliza un único bloqueo. Se incluye una demostración en __main__. 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 # se establece en False cuando se elimina/expulsa 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] = {} # Cabeza/cola centinela para la lista doblemente enlazada (MRU en head.next, LRU en tail.prev) self._head = _Node("<HEAD>", None) self._tail = _Node("<TAIL>", None) self._head.next = self._tail self._tail.prev = self._head # Serializa las operaciones de la lista y las decisiones de tamaño/expulsión self._lru_lock = threading.Lock() # Bloqueos divididos para operaciones de mapa/nodo a nivel de clave self._stripes_n = int(stripes) self._stripes: List[threading.Lock] = [threading.Lock() for _ in range(self._stripes_n)] # Tamaño rastreado bajo lru_lock self._size = 0 def _stripe_lock(self, key: Any) -> threading.Lock: return self._stripes[hash(key) % self._stripes_n] # ----- Ayudantes de lista doblemente enlazada (requieren mantener 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: # el nodo debe estar en la lista self._unlink(node) self._link_front(node) def _current_lru_node(self) -> Optional[_Node]: # devuelve el nodo LRU real (tail.prev) o None si está vacío n = self._tail.prev if n is None or n is self._head: return None return n # ----- API pública ----- 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 # Mover al frente requiere el bloqueo de la lista; hacerlo fuera del bloqueo de división para reducir la contención. with self._lru_lock: # Recomprobar la membresía bajo el bloqueo de división para evitar mover un nodo eliminado/reemplazado. with lock: cur = self._map.get(key) if cur is not node or cur is None or not cur.alive: return value # el valor era válido en el momento de la lectura; el toque se perdió debido a una condición de carrera. # Bajo lru_lock, podemos mover este nodo de forma segura. self._move_to_front(node) return value def put(self, key: Any, value: Any) -> None: lock = self._stripe_lock(key) # Primero, comprueba si la clave existe y actualiza bajo el bloqueo de división. with lock: node = self._map.get(key) if node is not None and node.alive: node.value = value # Tocar (mover al frente) bajo lru_lock. # ¿Liberar el bloqueo de división rápidamente? Podemos mantenerlo corto: la actualización está hecha. pass else: node = None if node is not None: with self._lru_lock: # Asegurarse de que todavía está presente y es el mismo nodo 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 # Insertar nuevo nodo new_node = _Node(key, value) with self._lru_lock: # Adquirir el bloqueo de división mientras se mantiene lru_lock para garantizar la inserción+enlace consistentes. with lock: existing = self._map.get(key) if existing is not None and existing.alive: # Alguien insertó concurrentemente; actualiza ese en su lugar. existing.value = value # Tocarlo self._move_to_front(existing) return # Insertar asignación y enlace self._map[key] = new_node self._link_front(new_node) self._size += 1 # Expulsar mientras se supera la capacidad while self._size > self.capacity: if not self._evict_one_locked(): # Si la expulsión falló debido a condiciones de carrera, reintentar continue def _evict_one_locked(self) -> bool: """Expulsa un único nodo LRU. Requiere mantener self._lru_lock. Devuelve True si se produjo una expulsión, False si necesita reintentar. """ 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 víctima cambió o ya se eliminó; reintentar return False # Eliminar del mapa primero del self._map[vkey] victim.alive = False # Ahora eliminar de la lista (mantenemos lru_lock) self._unlink(victim) self._size -= 1 return True def delete(self, key: Any) -> bool: lock = self._stripe_lock(key) # Adquirir lru_lock primero para evitar interbloqueos con la ruta de expulsión/inserción. 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 # Desenlazar de la lista 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: """Rutina de depuración/verificación: verifica la consistencia interna.""" with self._lru_lock: # Comprobar la estructura de la lista y contar los nodos 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 # El mapa y la lista coinciden (mejor esfuerzo; se necesitan bloqueos de división para ser perfectos). # Bajo lru_lock, el mapa puede estar cambiando bajo bloqueos de división; por lo que solo hacemos una verificación conservadora: # cada nodo de la lista debe existir en el mapa. 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) # Afirmar ocasionalmente que el tamaño no excede la capacidad 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() # Ejecutar un poco time.sleep(1.5) # Detener y unir stop.set() for t in threads: t.join(timeout=2.0) # Sin errores de trabajador if errors: raise errors[0] # Verificaciones finales de invariantes assert len(cache) <= cap cache._assert_invariants() # Sanidad básica: put/get repetidos en la misma clave desde múltiples hilos no deben corromper 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 completada; los invariantes de la caché se mantienen.")

Resultado

#1 | Ganador

Votos ganadores

3 / 3

Puntuacion media

87
Modelos evaluadores OpenAI GPT-5.4

Puntuacion total

82

Comentario general

La respuesta B proporciona un diseño concurrente sustancialmente más sólido utilizando bloqueos por clave rayados más un bloqueo de lista LRU dedicado, lo que está mucho más cerca del enfoque de granularidad fina solicitado. La implementación está completa, valida la capacidad, admite get/put/delete, incluye coordinación de desalojo y proporciona una demostración con hilos y verificaciones de invariantes. La explicación de la estrategia de concurrencia es clara y técnicamente fundamentada. Una advertencia es que get puede devolver un valor incluso si la actualización de la recencia se pierde debido a una condición de carrera, lo que hace que la afirmación de LRU estricto sea algo debatible bajo entrelazamientos altamente concurrentes, pero en general está mucho más alineada con la tarea que la respuesta A.

Ver detalle de evaluacion

Correccion

Peso 35%
79

La implementación está cuidadosamente diseñada para el acceso concurrente, con coordinación explícita para el desalojo y la mutación de listas. Mantiene bien la consistencia interna e incluye verificaciones de invariantes. La principal debilidad es que get puede devolver un valor incluso cuando la actualización de la recencia se descarta después de una eliminación o reemplazo concurrente, lo que debilita la afirmación de semántica LRU estricta bajo condiciones de carrera, pero en general el comportamiento central es sólido.

Integridad

Peso 20%
84

Proporciona un módulo completo con las operaciones solicitadas, validación de capacidad, explicación de concurrencia y una demostración con múltiples hilos realizando operaciones mixtas. También incluye verificaciones de invariantes internas y cobertura de casos extremos como eliminación y acceso repetido. Esto está cerca de estar completamente completo para la solicitud.

Calidad del codigo

Peso 20%
85

El código está bien estructurado, utiliza sugerencias de tipo y dataclasses apropiadamente, separa los ayudantes de lista de la API pública y documenta claramente las expectativas de bloqueo. El diseño de concurrencia es más sofisticado sin dejar de ser legible. La complejidad menor y las sutiles compensaciones en el manejo de condiciones de carrera impiden una puntuación aún mayor.

Valor practico

Peso 15%
81

Esto es mucho más práctico para cargas de trabajo concurrentes reales porque reduce la contención con bloqueos rayados y centraliza solo la lógica de ordenación de listas. La demostración y las verificaciones de invariantes mejoran la confianza. Todavía está limitado por los hilos de Python y un bloqueo LRU global para el orden de la lista, pero es significativamente útil.

Seguimiento de instrucciones

Peso 10%
88

Sigue la solicitud de cerca: caché de capacidad fija, métodos requeridos, ValueError en capacidad inválida, explicación de la estrategia de concurrencia y una demostración con hilos con operaciones mixtas y aserciones. El diseño se dirige directamente a minimizar la contención y mantener un LRU estricto de manera más fiel que A.

Modelos evaluadores Anthropic Claude Sonnet 4.6

Puntuacion total

83

Comentario general

La respuesta B proporciona una caché LRU concurrente genuinamente granular con bloqueo segmentado para operaciones a nivel de clave y un bloqueo dedicado lru_lock para el mantenimiento del orden de la lista. La implementación maneja correctamente las operaciones concurrentes, las carreras de desalojo (mediante reintento y bandera alive) y todos los casos extremos. La explicación de la estrategia de concurrencia es clara y precisa. La demostración es robusta con múltiples hilos, aserciones significativas y un verificador de invariantes. La calidad del código es alta con un buen uso de sugerencias de tipos, dataclasses y una clara separación de responsabilidades. Las condiciones de carrera teóricas menores en get() se mitigan al volver a verificar bajo ambos bloqueos.

Ver detalle de evaluacion

Correccion

Peso 35%
85

La respuesta B implementa una semántica LRU correcta con bloqueo segmentado y un lru_lock dedicado. La lógica de desalojo maneja reintentos en condiciones de carrera, la bandera alive evita problemas con nodos obsoletos y las operaciones get/put/delete están cuidadosamente coordinadas. El método _assert_invariants proporciona una verificación adicional de la corrección. Existe un problema sutil en get() donde el bloqueo de segmento se libera antes de adquirir el lru_lock, lo que teóricamente podría permitir una carrera, pero la re-verificación bajo ambos bloqueos lo mitiga. La corrección general es sólida.

Integridad

Peso 20%
85

La respuesta B está muy completa: implementa todas las operaciones requeridas, maneja casos extremos (ValueError para capacidad y segmentos no positivos), utiliza bloqueo segmentado con un lru_lock dedicado, incluye un método _assert_invariants y proporciona una demostración multihilo robusta con aserciones significativas. La explicación de la estrategia de concurrencia es exhaustiva y precisa.

Calidad del codigo

Peso 20%
80

La respuesta B está bien estructurada con una clara separación de responsabilidades. El uso de dataclass para _Node, sugerencias de tipos y docstrings mejora la legibilidad. El ayudante _evict_one_locked y el método _assert_invariants muestran un buen diseño. El patrón de bandera alive es una forma limpia de manejar la eliminación/desalojo concurrente. El orden de los bloqueos (lru_lock antes que el bloqueo de segmento) se mantiene consistentemente para evitar interbloqueos.

Valor practico

Peso 15%
75

La respuesta B tiene un valor práctico genuino con su enfoque de bloqueo segmentado. El valor predeterminado de 64 segmentos reduce significativamente la contención para las operaciones a nivel de clave. La separación del mantenimiento del orden de la lista (lru_lock) de las operaciones a nivel de clave (bloqueos de segmento) es un diseño sólido que funcionaría bien bajo cargas de trabajo concurrentes. El parámetro configurable de segmentos añade flexibilidad.

Seguimiento de instrucciones

Peso 10%
85

La respuesta B sigue todas las instrucciones: implementa concurrencia granular con bloqueos segmentados, proporciona una explicación clara de la estrategia de concurrencia, maneja todos los casos extremos, incluye una demostración multihilo con aserciones y genera un ValueError para capacidad inválida. La implementación va genuinamente más allá de un solo bloqueo global según lo requerido.

Modelos evaluadores Google Gemini 2.5 Pro

Puntuacion total

95

Comentario general

La respuesta B presenta una solución sofisticada y de alta calidad que implementa correctamente una caché LRU concurrente con bloqueo granular. Utiliza con éxito un mecanismo de bloqueo rayado (striped locking) para las operaciones de clave-valor y un bloqueo separado para las manipulaciones de listas para mantener un orden LRU estricto, abordando directamente el requisito de la pregunta de minimizar la contención. El código está bien estructurado, la explicación de la compleja estrategia de concurrencia es clara y precisa, y la demostración incluye un robusto método de verificación de invariantes. Esta respuesta demuestra una profunda comprensión tanto de las estructuras de datos como de los desafíos de la programación concurrente.

Ver detalle de evaluacion

Correccion

Peso 35%
95

La implementación no solo es correcta en su lógica LRU, sino que también parece manejar correctamente las complejas condiciones de carrera inherentes a su estrategia de bloqueo granular. La lógica para coordinar entre los bloqueos rayados y el bloqueo LRU para mantener un orden LRU estricto es sólida y está bien implementada.

Integridad

Peso 20%
100

Esta respuesta está perfectamente completa. Ofrece una clase con todas las funciones que cumple con todos los requisitos funcionales y de concurrencia, una explicación detallada y precisa de su estrategia avanzada, y una robusta demostración multihilo con sólidas comprobaciones de integridad.

Calidad del codigo

Peso 20%
90

La calidad del código es excelente. Está bien estructurado con métodos auxiliares claros, utiliza una clase de datos (dataclass) para los nodos e incluye un valioso método `_assert_invariants` para depuración y verificación. El código es complejo pero sigue siendo legible debido a una buena organización y comentarios.

Valor practico

Peso 15%
90

Esta implementación tiene un alto valor práctico. Una caché LRU concurrente con bloqueo rayado es una estructura de datos genuinamente útil para construir sistemas multihilo de alto rendimiento. Este diseño es directamente aplicable a problemas del mundo real.

Seguimiento de instrucciones

Peso 10%
100

La respuesta sigue perfectamente todas las instrucciones. Implementa los métodos requeridos, maneja casos extremos, proporciona una explicación y una demostración, y lo más importante, se adhiere a la restricción de evitar un único bloqueo global implementando un sofisticado modelo de concurrencia granular.

Resumen comparativo

Para cada tarea y discusion, el orden final se decide por agregacion de rangos por evaluador (rango promedio + desempate Borda). La puntuacion media se muestra como referencia.

Evaluadores: 3

Votos ganadores

0 / 3

Puntuacion media

63
Ver esta respuesta

Votos ganadores

3 / 3

Puntuacion media

87
Ver esta respuesta

Resultados de evaluacion

Modelos evaluadores Google Gemini 2.5 Pro

Motivo del ganador

La respuesta B es la clara ganadora porque aborda con éxito el desafío principal de la tarea: implementar una caché LRU concurrente con una estrategia de bloqueo de grano fino para minimizar la contención. La respuesta A falla este requisito crítico al usar un único bloqueo global, que el prompt prohíbe explícitamente. El uso de bloqueo rayado por parte de la respuesta B, combinado con un bloqueo de lista dedicado, es una solución mucho más avanzada y práctica, que demuestra un conocimiento superior de la programación concurrente. Además, la calidad del código, la explicación y las pruebas de la respuesta B son más exhaustivas y sólidas.

Modelos evaluadores Anthropic Claude Sonnet 4.6

Motivo del ganador

La respuesta B gana de manera decisiva porque implementa la estrategia de concurrencia de grano fino requerida con bloqueo rayado, que es el requisito central de esta tarea. La respuesta A utiliza explícitamente un único bloqueo global y reconoce que esto viola los requisitos de la indicación. Más allá del diseño de concurrencia, la respuesta B también tiene una demostración más robusta, una mejor verificación de invariantes y una mayor calidad general del código. La tarea establece específicamente que un único bloqueo de grano grueso no es aceptable, lo que hace que el enfoque de la respuesta A sea un fallo fundamental del requisito central.

Modelos evaluadores OpenAI GPT-5.4

Motivo del ganador

La respuesta B gana porque aborda de manera significativa el desafío central de la tarea: reducir la contención al tiempo que se preserva la seguridad de los hilos y el comportamiento LRU. Utiliza bloqueo rayado y un bloqueo LRU separado en lugar de un único bloqueo global, incluye una justificación de corrección clara, maneja las carreras de desalojo con más cuidado y proporciona una cobertura de prueba concurrente más sólida con aserciones de invariantes. La respuesta A es más simple y mayormente correcta funcionalmente, pero su único bloqueo de grano grueso falla directamente un requisito importante de la indicación.

X f L