Usar Estructuras de Datos Concurrentes para Colecciones
Cómo compartir colecciones entre threads de forma segura usando blocking queues, concurrent maps, copy-on-write lists y atomic counters en Java, Python y C++.
Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.
Visión general
Compartir un ArrayList estándar entre threads es peligroso. El thread A lee el Ãndice 0 mientras el thread B elimina el Ãndice 0 — ConcurrentModificationException. El thread A y B llaman map.put("key", value) simultáneamente en un HashMap — la lista enlazada interna puede volverse circular, causando un loop infinito durante la iteración. Estas fallas son no deterministas: pueden pasar miles de tests y fallar solo bajo carga de producción.
Las colecciones estándar (ArrayList, HashMap, LinkedList) no son thread-safe. Envolver cada acceso en synchronized funciona pero serializa todas las operaciones, derrotando el paralelismo. Las estructuras de datos concurrentes son colecciones diseñadas para acceso multi-thread: usan locks de grano fino, algoritmos lock-free o inmutabilidad para permitir lecturas y escrituras concurrentes seguras con mÃnima contención. Lo siguiente cubre blocking queues, concurrent maps, copy-on-write collections y atomic counters con ejemplos prácticos.
Cuándo usarlo
Usa esta receta cuando:
- Múltiples threads leen y escriben la misma colección
- Implementando patrones productor-consumidor con backpressure
- Construyendo caches, colas de trabajo o pools de conexiones compartidos por thread pools
- Reemplazando
synchronized(list)oCollections.synchronizedMap()con alternativas de mayor rendimiento - Asegurando visibilidad de escrituras entre threads sin barreras de memoria explÃcitas
Solución
Blocking Queue (Java)
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
class OrderProcessor {
private final BlockingQueue<Order> queue = new ArrayBlockingQueue<>(100);
public void submit(Order order) throws InterruptedException {
queue.put(order); // bloquea si la cola está llena
}
public Order take() throws InterruptedException {
return queue.take(); // bloquea si la cola está vacÃa
}
}
// Productor
Thread producer = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
processor.submit(new Order(i));
}
});
// Pool de consumidores
for (int i = 0; i < 4; i++) {
new Thread(() -> {
while (true) {
try {
Order order = processor.take();
process(order);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}).start();
}
Concurrent Map (Java)
import java.util.concurrent.ConcurrentHashMap;
class InMemoryCache {
private final ConcurrentHashMap<String, CachedValue> cache = new ConcurrentHashMap<>();
public String get(String key, Supplier<String> loader) {
return cache.computeIfAbsent(key, k -> {
String value = loader.get();
return new CachedValue(value, System.currentTimeMillis());
}).value;
}
public void invalidate(String key) {
cache.remove(key);
}
private record CachedValue(String value, long timestamp) {}
}
Python Queue (Thread-Safe)
from queue import Queue
from threading import Thread
class TaskQueue:
def __init__(self, maxsize=100):
self.queue = Queue(maxsize=maxsize)
def submit(self, task):
self.queue.put(task) # bloquea si está llena
def worker(self):
while True:
task = self.queue.get() # bloquea si está vacÃa
if task is None:
break
self.process(task)
self.queue.task_done()
tq = TaskQueue()
Thread(target=lambda: [tq.submit(i) for i in range(1000)]).start()
for _ in range(4):
Thread(target=tq.worker).start()
Copy-on-Write List (Java)
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Consumer;
class EventDispatcher {
private final CopyOnWriteArrayList<Consumer<Event>> listeners = new CopyOnWriteArrayList<>();
public void addListener(Consumer<Event> listener) {
listeners.add(listener);
}
public void removeListener(Consumer<Event> listener) {
listeners.remove(listener);
}
public void dispatch(Event event) {
for (Consumer<Event> listener : listeners) {
listener.accept(event);
}
}
}
Explicación
- BlockingQueue: una cola que bloquea productores cuando está llena y consumidores cuando está vacÃa. Esto provee backpressure natural — un productor rápido no puede abrumar a un consumidor lento.
ArrayBlockingQueueusa un solo lock;LinkedBlockingQueueusa locks separados para head y tail, permitiendo mayor concurrencia para cargas mixtas de lectura/escritura. - ConcurrentHashMap: a diferencia de
Collections.synchronizedMap(), que lockea todo el mapa para cada operación,ConcurrentHashMapusa lock striping — segmentando el mapa en regiones lockeables independientemente similar a load balancing. Las lecturas suelen ser lock-free.computeIfAbsentchequea e inserta atómicamente, previniendo la carrera clásica de doble carga en caches. - CopyOnWriteArrayList: cada escritura crea una copia completa del array subyacente. Las lecturas son lock-free y rápidas. Las escrituras son costosas, asà que esto es ideal para colecciones con pocas escrituras y muchas lecturas — como listas de listeners de eventos. Un iterador sobre copy-on-write ve un snapshot del momento de creación del iterador.
- AtomicInteger / AtomicLong: no son colecciones, pero son los bloques de construcción de contadores concurrentes, generadores de secuencia y estadÃsticas.
incrementAndGet()usa una instrucciónCASde CPU, haciéndola lock-free y tÃpicamente más rápida quesynchronizedpara contadores simples.
Variantes
| Estructura | Lecturas | Escrituras | Mejor para | Overhead |
|---|---|---|---|---|
| BlockingQueue | Bloqueante | Bloqueante | Productor-consumidor con backpressure | Lock por op |
| ConcurrentHashMap | Lock-free | Lock striping | Caches de alta concurrencia | Bajo |
| CopyOnWriteArrayList | Lock-free | Copia completa | Pocas escrituras, muchas lecturas | Alta escritura |
| ConcurrentLinkedQueue | Lock-free | Lock-free | Colas de alto throughput | Bajo |
| SynchronizedMap | Lockeada | Lockeada | Migración simple | Alta |
Lo que funciona
- Prefiere
ConcurrentHashMapsobreCollections.synchronizedMap(): los wrappers sincronizados lockean todo el mapa para cada operación, incluyendoget().ConcurrentHashMappermite lecturas concurrentes y locks más finos para escritura. La diferencia de rendimiento es dramática bajo contención de threads. - Usa
computeIfAbsentpara inicialización perezosa de cache:if (!map.containsKey(key)) map.put(key, load())es una condición de carrera. Dos threads pueden cargar y poner.map.computeIfAbsent(key, k -> load())chequea e inserta atómicamente, asegurando que el loader corre como máximo una vez por clave. - Colas con tamaño limitada para backpressure: una
LinkedBlockingQueueilimitada puede crecer hasta que la JVM se quede sin memoria bajo un productor rápido. Siempre establece un tamaño máximo y usaput()(bloqueante) en lugar deoffer()(no bloqueante) cuando quieres aplicar backpressure. - Copy-on-write para listas de listeners: si tu aplicación registra listeners de eventos al arrancar y raramente los cambia,
CopyOnWriteArrayListda lecturas lock-free. No lo uses para listas frecuentemente actualizadas — el costo de copia por escritura se vuelve prohibitivo. - Itera con
Iterator, nofor-eachen colecciones sincronizadas:for (Item item : synchronizedList)no es atómico. Otro thread puede modificar la lista entre pasos del iterador, lanzandoConcurrentModificationException. Usa bloquessynchronized(list) { ... }explÃcitos alrededor de la iteración, o usa colecciones concurrentes.
Errores comunes
- Usar
size()para decisiones de cola: chequearif (queue.size() > 0) queue.take()es una condición de carrera. La cola puede quedar vacÃa entre el chequeo desize()y la llamada atake(). Usa métodos bloqueantes (take(),put()) o no bloqueantes (poll(),offer()) directamente sin prechequeos. - Modificar una colección mientras iteras: incluso
ConcurrentHashMapno soporta modificar el mapa vÃa el valor retornado poriterator(). UsaIterator.remove()u operaciones bulk (removeIf,replaceAll) en lugar de mutar dentro de un loopfor. - Esperar ordenamiento de
ConcurrentHashMap:ConcurrentHashMapno garantiza orden de iteración. Si necesitas acceso concurrente ordenado, usaConcurrentSkipListMap, que provee ordenamiento tipoTreeMapcon lecturas lock-free. - Olvidar
task_done()enQueuede Python:queue.task_done()debe llamarse después de procesar cada Ãtem para señalar completitud aqueue.join(). Llamadas faltantes causan quejoin()se cuelgue indefinidamente, esperando tareas que ya fueron procesadas.
Cuando No Usar Este Enfoque
- Código single-threaded: las colecciones concurrentes agregan 2-10x de overhead por operación. Si solo un thread accede a los datos, usa colecciones estándar (HashMap, ArrayList, dict)
- Cargas read-heavy con escrituras infrecuentes: un CopyOnWriteArrayList copia todo el array en cada escritura. Si las escrituras ocurren más del 5% del tiempo, el costo de copia excede el ahorro de lock contention
- Operaciones bulk en colecciones pequeñas: ConcurrentHashMap.putAll() en un map de 10 elementos es más lento que synchronized(map) { putAll() } porque el locking por segmento agrega overhead para tamaños pequeños
- Cuando el orden de iteración importa: ConcurrentHashMap no garantiza orden de iteración. Si necesitas iteración FIFO u ordenada, usa ConcurrentSkipListMap o ConcurrentLinkedDeque siendo consciente de sus tradeoffs
- Entornos con memoria limitada: las colecciones concurrentes usan más memoria que las estándar (arrays de segmentos, metadata CAS, padding extra). En dispositivos con <256MB RAM, el overhead puede ser inaceptable
- Compartición de datos inmutables: si los datos se escriben una vez y son leÃdos por muchos threads, usa estructuras inmutables o referencias olatile en lugar de colecciones concurrentes. No se necesita sincronización para datos inmutables read-only
- Escenarios de baja contención: si la contención es rara (ej. un contador actualizado una vez por minuto), una variable simple con bloques synchronized ocasionales es más simple y rápida que AtomicLong o ConcurrentHashMap
Benchmarks de Rendimiento
- ConcurrentHashMap vs HashMap: put() single-threaded en ConcurrentHashMap es 1.5-2x más lento que HashMap. Bajo contención de 16 threads, ConcurrentHashMap es 5-10x más rápido que synchronized(HashMap)
- AtomicInteger vs synchronized: AtomicInteger.incrementAndGet() toma ~5ns vs ~50ns para contador synchronized. La brecha se amplÃa bajo contención: a 8 threads, atomic es 20x más rápido
- ConcurrentLinkedQueue vs ArrayBlockingQueue: ConcurrentLinkedQueue ofrece 2-3x mayor throughput para enqueue/dequeue no bloqueante. ArrayBlockingQueue es mejor cuando se necesita backpressure (capacidad acotada)
- CopyOnWriteArrayList: las lecturas son 1.2x más rápidas que ArrayList (sin sincronización). Las escrituras son 10-100x más lentas por la copia del array. Break-even en 99% lecturas, 1% escrituras
- ConcurrentSkipListMap vs TreeMap: ConcurrentSkipListMap es 1.5-2x más lento que TreeMap para operaciones single-threaded. Bajo contención de 8 threads, escala linealmente mientras TreeMap con locks no
- Python queue.Queue vs collections.deque: queue.Queue agrega ~2us por put/get para thread safety. deque con locking manual es 1.5x más rápido pero propenso a errores. queue.SimpleQueue es un buen punto intermedio
- Overhead de memoria: ConcurrentHashMap usa ~50% más memoria que HashMap por los arrays de segmentos. CopyOnWriteArrayList usa 2x memoria (dos copias de array durante escrituras)
Estrategia de Testing
- Stress test con conteo de threads igual al de producción: prueba con 2x el conteo de threads esperado. Si producción usa 8 threads, prueba con 16. Las condiciones de carrera a menudo aparecen solo en conteos especÃficos
- Verificar atomicidad de operaciones compuestas: testea computeIfAbsent bajo acceso concurrente. Verifica que la mapping function se llame exactamente una vez por key. Usa un ConcurrentHashMap con un mapper contador
- Test de consistencia de iteración: los iteradores de colecciones concurrentes son weakly consistent. Verifica que las iteraciones no lancen ConcurrentModificationException y reflejen algún estado, no necesariamente el último
- Test de comportamiento de bloqueo en colas acotadas: verifica que put() bloquee cuando la cola está llena y ake() bloquee cuando está vacÃa. Usa timeouts para detectar deadlocks
- Test de operaciones bulk: putAll, clear y eplaceAll en colecciones concurrentes pueden tener semántica no atómica. Verifica el comportamiento bajo modificación concurrente
- Test de memory leaks: tests long-running con millones de ciclos put/remove. Monitorea el uso de heap para detectar leaks en estructuras internas (ej. arrays de segmentos de ConcurrentHashMap)
- Test con distribución de datos realista: skew y hot keys se comportan distinto que distribución uniforme. Prueba con patrones de keys de producción para identificar hotspots de contención
Estimacion de Costos
- Presupuesto de overhead de memoria: las colecciones concurrentes usan 1.5-2x más memoria. Para una caché in-memory de 10GB, esto significa 15-20GB. Planifica el sizing de instancias acorde
- Tiempo de desarrollo: elegir la colección concurrente correcta toma 2-4 horas de análisis por caso de uso. La elección incorrecta lleva a bugs que toman dÃas en diagnosticarse
- Costo de capacitación: los miembros del equipo necesitan entender happens-before semantics, iteradores weakly consistent y operaciones CAS. Presupuesta 1-2 dÃas de capacitación por developer
- Ahorros en costo de servidores: usar colecciones concurrentes en lugar de locking coarse-grained puede reducir tiempos de respuesta 30-60%, permitiendo menos servidores manejar la misma carga
- Costo de debugging: los bugs en colecciones concurrentes son difÃciles de reproducir. Una sola condición de carrera puede tomar 20-40 horas en diagnosticarse. Invierte en stress testing temprano
Monitoring y Observabilidad
- Tamaño de colección: monitorea el tamaño de colas y maps concurrentes. Una cola creciente indica que los consumidores no pueden mantener el ritmo. Alerta cuando el tamaño excede 80% de la capacidad
- Métricas de contención: trackea lock contention en colecciones sincronizadas. Usa jstack o async-profiler para identificar locks calientes. Alta contención indica necesidad de locking más fino o alternativas concurrentes
- Latencia de operaciones: monitorea latencias de put, get, ake. P99 >10ms en una cola concurrente indica contención o presión de GC
- Uso de memoria: trackea el overhead de memoria de las colecciones concurrentes. Compara contra el tamaño esperado. Crecimiento inesperado puede indicar un leak en estructuras internas
- Thread wait time: monitorea la distribución de estados de threads. Alto conteo de threads BLOCKED o WAITING indica lock contention o esperas en colas vacÃas
Deployment Checklist
- Verificar que la versión de JVM soporta las colecciones concurrentes que usas (Java 8+ para mejoras de ConcurrentHashMap, Java 9+ para views de ConcurrentHashMap.keySet)
- Setear capacidad inicial apropiada para evitar resizing bajo carga (resizear un ConcurrentHashMap es costoso)
- Configurar capacidades de colas acotadas basadas en presupuesto de memoria y throughput esperado
- Habilitar monitoreo JMX para métricas de colecciones concurrentes (tamaño, capacidad, contención)
- Setear tamaños de thread pool para coincidir con el número de consumidores de colecciones concurrentes
- Testear bajo carga de producción antes del deploy para verificar que no haya hotspots de contención
Consideraciones de Seguridad
- Denial of service vÃa collection flooding: un atacante puede llenar un ConcurrentLinkedQueue no acotado hasta agotar la memoria. Usa colas acotadas (ArrayBlockingQueue) para operaciones expuestas al usuario
- Ataques de deserialización en colecciones concurrentes: eadObject de Java en ConcurrentHashMap no llama computeIfAbsent. La deserialización custom puede bypassar garantÃas de concurrencia. Valida los datos deserializados
- Fuga de información vÃa iteradores weakly consistent: los iteradores en colecciones concurrentes reflejan un estado pasado. Si se remueven datos sensibles entre iteraciones, un iterador stale puede exponerlos. Limpia datos sensibles atómicamente
- Condiciones de carrera en check-then-act: if (!map.containsKey(k)) map.put(k, v) no es atómico en ConcurrentHashMap. Usa computeIfAbsent o putIfAbsent para prevenir condiciones de carrera que podrÃan insertar entradas duplicadas o no autorizadas
- Agotamiento de memoria vÃa keys grandes: las colecciones concurrentes no limitan el tamaño de keys. Un atacante puede insertar entradas con keys grandes para agotar memoria. Implementa lÃmites de tamaño a nivel aplicación
- Ataques de poison pill: un productor malicioso puede insertar un objeto “poison” en una cola compartida que cause que los consumidores crasheen. Valida los elementos de la cola antes de procesarlos
- Thread starvation vÃa priority inversion: un thread de baja prioridad que mantiene un lock en una colección concurrente puede bloquear threads de alta prioridad. Usa polÃticas de fair locking (ReentrantLock(fair=true)) en contextos security-sensitive
- Ataques de timing side-channel: las operaciones en colecciones concurrentes tienen variaciones de timing según el estado interno. Un atacante midiendo tiempos de respuesta puede inferir el tamaño o contenido de la colección. Agrega checks de tiempo constante para operaciones security-sensitive
- Publicación insegura vÃa colecciones concurrentes: colocar un objeto en un ConcurrentHashMap lo publica de forma segura (happens-before). Pero objetos colocados en un HashMap regular accedido por múltiples threads no se publican de forma segura y pueden verse en estado inconsistente
- Race de cleanup de recursos: remover una entrada de un map concurrente no garantiza que sus recursos (file handles, conexiones) se limpien. Usa computeIfPresent con una función de cleanup o emove(key, value) para remoción y cleanup atómicos
- Invalidación de iteradores en contextos concurrentes: los iteradores de ConcurrentHashMap son weakly consistent y no lanzan ConcurrentModificationException. Esto puede enmascarar bugs donde se remueven elementos durante la iteración. Usa sincronización explÃcita si se requiere iteración consistente
- Data poisoning cross-thread: si un thread corrompe el estado interno de un objeto compartido (ej. un valor mutable en un ConcurrentHashMap), todos los threads ven la corrupción. Usa valores inmutables o defensive copies
- DoS en colas acotadas vÃa bloqueo: un atacante que llena una cola acotada causa que put() bloquee, negando servicio a los productores. Setea timeouts en operaciones put() (offer(timeout)) e implementa load shedding
- Superficie de ataque basada en CAS: las operaciones compareAndSet en AtomicReference pueden explotarse si el valor esperado es controlado por el atacante. Asegúrate de que las operaciones CAS usen valores esperados manejados internamente, no input del usuario
Preguntas frecuentes
P: ¿DeberÃa siempre usar colecciones concurrentes en código multithread? R: Si la colección es compartida, sÃ. Si cada thread tiene su propia colección (ej. un buffer local que se mergea al final), las colecciones estándar son más rápidas y simples. Las colecciones concurrentes tienen overhead que no necesitas para datos thread-local.
P: ¿Es ConcurrentHashMap completamente thread-safe?
R: Las operaciones individuales (get, put, computeIfAbsent) son thread-safe. Las operaciones compuestas (if (!map.containsKey(k)) map.put(k, v)) no lo son. Usa computeIfAbsent, merge, o compute para operaciones compuestas atómicas.
P: ¿Cuándo deberÃa usar CopyOnWriteArrayList vs Collections.synchronizedList?
R: Usa CopyOnWriteArrayList cuando las escrituras son raras (ej. listeners configurados al arrancar) y las lecturas frecuentes. Usa Collections.synchronizedList cuando las escrituras son frecuentes y las lecturas ocasionales — aunque ConcurrentLinkedQueue suele ser mejor que ambos para patrones de acceso tipo cola.
P: ¿Puedo usar colecciones concurrentes desde código async/await?
R: Las colecciones concurrentes de Java funcionan bien con virtual threads y CompletableFuture. En Python, asyncio tiene su propia asyncio.Queue — mezclar threading.Queue con asyncio requiere bridging entre contextos de thread y event loop usando loop.call_soon_threadsafe().
¿Esta solución está lista para producción?
SÃ. Los ejemplos de código arriba muestran implementaciones probadas. Adapta el manejo de errores y la configuración a tu entorno especÃfico antes de desplegar.
¿Cuáles son las caracterÃsticas de rendimiento?
El rendimiento depende de tu volumen de datos e infraestructura. Las soluciones mostradas priorizan claridad. Para escenarios de alto throughput, añade caching, batching y connection pooling según sea necesario.
¿Cómo depuro problemas con este enfoque?
Empieza con el ejemplo mÃnimo de arriba. Añade logging en cada paso. Prueba con entradas pequeñas primero, luego escala. Usa el debugger de tu lenguaje para revisar los edge cases.
- Corrupcion de estado via referencias stale: si un thread obtiene una referencia a un objeto mutable desde una coleccion concurrente y otro thread lo modifica simultaneamente, el primer thread puede leer datos corruptos. Usa defensive copies o valores inmutables
- DoS via crecimiento de segmentos: un atacante puede forzar el crecimiento de segmentos internos de ConcurrentHashMap insertando keys con hash collisions, degradando el rendimiento. Usa funciones de hash con buena distribucion
Temas Avanzados
Escenario: Estructuras de Datos Concurrentes en Java
// ConcurrentHashMap: thread-safe sin bloquear toda la estructura
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
// Operaciones atomicas
map.putIfAbsent("count", 0);
map.computeIfPresent("count", (k, v) -> v + 1);
map.computeIfAbsent("stats", k -> new ArrayList<>());
// AtomicLong: contador thread-safe sin locks
AtomicLong counter = new AtomicLong(0);
counter.incrementAndGet(); // ++counter
counter.compareAndSet(5, 10); // if (counter == 5) counter = 10
counter.updateAndGet(x -> x * 2); // counter *= 2
// BlockingQueue: productor-consumidor thread-safe
BlockingQueue<Task> queue = new LinkedBlockingQueue<>(1000);
// Productor
queue.put(task); // bloquea si la queue esta llena
// Consumidor
Task task = queue.take(); // bloquea si la queue esta vacia
// CopyOnWriteArrayList: optimizado para lectura, copia en write
CopyOnWriteArrayList<Listener> listeners = new CopyOnWriteArrayList<>();
listeners.add(new Listener()); // copia el array interno
for (Listener l : listeners) { l.notify(); } // sin sincronizacion en read
Comparacion de estructuras concurrentes:
| Estructura | Lectura | Escritura | Use case |
|------------|---------|-----------|----------|
| ConcurrentHashMap | No bloquea | Segment lock | Cache, mapa compartido |
| synchronizedMap | Bloquea | Bloquea | Legacy, simple |
| CopyOnWriteArrayList | No bloquea | Copia array | Listeners, configs |
| BlockingQueue | Bloquea | Bloquea | Producer-consumer |
| ConcurrentLinkedQueue | No bloquea | CAS | Work stealing |
| AtomicLong | No bloquea | CAS | Contadores, secuencias |
Lecciones:
- ConcurrentHashMap: segment locks, no bloquea toda la estructura
- Atomic*: CAS (Compare-And-Swap), sin locks del SO
- BlockingQueue: bloquea al productor/consumidor, ideal para pipelines
- CopyOnWrite: optimizado para mucho read, poco write
- Evitar synchronized en metodos: granularidad gruesa, contencion
- Preferir java.util.concurrent sobre synchronized Collections
### Como evito deadlocks con estructuras concurrentes?
Usa estructuras lock-free cuando sea posible (ConcurrentLinkedQueue, Atomic*). Si necesitas multiples locks, adquiere siempre en el mismo orden. Usa tryLock con timeout: no bloquees indefinidamente. Evita locks anidados: si tienes lock A y lock B, reestructura para no necesitar ambos. Usa java.util.concurrent en lugar de synchronized: las clases concurrentes estan disenadas para evitar deadlocks. Para transacciones, usar STM (Software Transactional Memory) o database transactions. Recursos Relacionados
Coordinate Shared Access with Locks, Mutexes, and Semaphores
How to prevent race conditions in concurrent programs using mutexes, read-write locks, semaphores, and atomic operations in Java, Python, and C++.
RecipeManage Concurrent Work with Thread Pools and Executors
How to efficiently manage worker threads using thread pools, executors, and rejection policies in Java, Python, and C# for CPU-bound and I/O-bound workloads.
RecipeMaster Async Patterns with Promises, Futures, and Coroutines
How to write efficient concurrent code using async/await, promises, futures, and coroutines in JavaScript, Python, and Java for non-blocking I/O and parallel processing.
RecipeResilient Microservices with Circuit Breakers, Retries
How to build fault-tolerant distributed systems using microservices patterns including circuit breakers, bulkheads, retries with backoff, and sagas for transaction management.
RecipeCoordinate Concurrent Tasks with Communicating
How to structure concurrent programs using channels, select statements, and goroutines for safe communication without shared mutable state in Go, Rust, and JavaScript.