Patrones Async con Promises, Futures y Coroutines
Escribe código concurrente eficiente con async/await, promises, futures y coroutines en JavaScript, Python y Java para I/O no bloqueante.
Visión general
El código síncrono bloquea el hilo de ejecución hasta que una operación termina. Cuando esa operación es I/O — consultar una base de datos, llamar a una API, leer un archivo — el hilo queda inactivo, desperdiciando ciclos de CPU que podrían atender otras peticiones. La programación async lo resuelve suspendiendo la tarea actual al encontrar I/O, dejando que el runtime ejecute otro trabajo, y reanudándola cuando el I/O completa. Así, un solo hilo puede mantener miles de conexiones concurrentes.
La parte difícil no es escribir async y await — es entender el event loop debajo, evitar el callback hell, propagar errores a través de los puntos de suspensión y acotar la concurrencia para no agotar recursos compartidos. Cada runtime lo hace distinto: JavaScript usa un event loop con promises, Python tiene coroutines con asyncio, Java tiene CompletableFuture sobre pools de hilos. Abajo están los patrones, anti-patrones e implementaciones que funcionan en los tres.
Cuándo usarlo
Usa esta receta cuando:
- Construyes APIs que atienden cientos de peticiones concurrentes por proceso
- Obtienes datos de varios servicios independientes que pueden llamarse en paralelo
- Procesas cargas I/O-bound como web scraping, subida de archivos o colas de mensajes
- Implementas capacidades en tiempo real como WebSockets, chat o dashboards en vivo
- Reemplazas un modelo thread-per-request por una arquitectura event-driven para reducir servidores
Solución
Async/Await con peticiones concurrentes (JavaScript / Node.js)
async function fetchUserDashboard(userId) {
const [profile, orders, recommendations] = await Promise.all([
getProfile(userId),
getOrders(userId),
getRecommendations(userId),
]);
return { profile, orders, recommendations };
}
async function fetchDashboardResilient(userId) {
const [profile, orders, recommendations] = await Promise.allSettled([
getProfile(userId),
getOrders(userId),
getRecommendations(userId),
]);
return {
profile: profile.status === 'fulfilled' ? profile.value : null,
orders: orders.status === 'fulfilled' ? orders.value : [],
recommendations: recommendations.status === 'fulfilled' ? recommendations.value : [],
};
}
Python asyncio con Task Groups
import asyncio
import aiohttp
async def fetch_url(session: aiohttp.ClientSession, url: str) -> dict:
async with session.get(url) as response:
return await response.json()
async def fetch_all_urls(urls: list[str]) -> list[dict]:
async with aiohttp.ClientSession() as session:
async with asyncio.TaskGroup() as tg:
tasks = [tg.create_task(fetch_url(session, url)) for url in urls]
return [task.result() for task in tasks]
async def fetch_with_limit(urls: list[str], max_concurrent: int = 10):
semaphore = asyncio.Semaphore(max_concurrent)
async def bounded_fetch(session, url):
async with semaphore:
return await fetch_url(session, url)
async with aiohttp.ClientSession() as session:
return await asyncio.gather(
*[bounded_fetch(session, url) for url in urls]
)
urls = ["https://api.example.com/users/1", "https://api.example.com/users/2"]
results = asyncio.run(fetch_all_urls(urls))
Pipeline con CompletableFuture en Java
import java.util.concurrent.CompletableFuture;
public class AsyncOrderService {
public CompletableFuture<Order> processOrder(String orderId) {
return validateOrder(orderId)
.thenCompose(this::checkInventory)
.thenCompose(this::processPayment)
.thenCompose(this::createShipment)
.exceptionally(ex -> {
log.error("Order processing failed", ex);
return Order.failed(orderId, ex.getMessage());
});
}
private CompletableFuture<ValidatedOrder> validateOrder(String orderId) {
return CompletableFuture.supplyAsync(() -> new ValidatedOrder(orderId));
}
public CompletableFuture<Dashboard> loadDashboard(String userId) {
CompletableFuture<Profile> profileFuture = fetchProfile(userId);
CompletableFuture<List<Order>> ordersFuture = fetchOrders(userId);
return profileFuture.thenCombine(ordersFuture, Dashboard::new);
}
}
Explicación
- Event loop: el mecanismo detrás del async de JavaScript y de
asyncioen Python. Mantiene una cola de tareas listas y las ejecuta de una en una. Cuando una tarea llega a unawait, cede el control; el loop toma la siguiente tarea lista; cuando el I/O esperado termina, la tarea suspendida vuelve a la cola. Un solo hilo, sin coste de cambio de contexto por tarea. - Concurrencia estructurada: en Python 3.11+,
asyncio.TaskGroupcancela todas las tareas hermanas si una hija falla, así nunca quedan tareas huérfanas en segundo plano tras un error del padre. JavaScript no tiene un equivalente incorporado —Promise.allfalla rápido pero no cancela el resto, así queAbortControlleres la vía manual. - Composición de promises:
Promise.all()espera a todas y rechaza ante el primer fallo;Promise.allSettled()espera a todas y reporta cada resultado;Promise.race()devuelve la primera en resolverse. Los equivalentes enasynciosongather,TaskGroupywaitconFIRST_COMPLETED. - Backpressure con semáforos: la concurrencia sin límites agota memoria, file descriptors y cuotas del servicio externo. Un semáforo acota las operaciones simultáneas — con límite de 10, la undécima petición espera un slot libre en lugar de competir por sockets.
Variantes
| Patrón | Lenguaje | Modelo de concurrencia | Manejo de errores | Mejor para |
|---|---|---|---|---|
| async/await | JS/Python | Event loop | try/catch | APIs I/O-bound |
| CompletableFuture | Java | Pool de hilos | exceptionally() | CPU + I/O mixto |
| Goroutines | Go | Hilos M:N | Channels | Servicios de alto throughput |
| RxJS/RxPY | JS/Python | Observables | onError | Streams de eventos |
| Hilos | Todos | Hilos del SO | try/catch | Tareas CPU-bound |
La diferencia real es entre concurrencia de event loop (un hilo, scheduling cooperativo) y concurrencia de pool de hilos (apropiativa, planificada por el SO). Las goroutines quedan en el medio: el runtime de Go mapea goroutines sobre unos pocos hilos del SO, por eso Go escala distinto que Node.js con el mismo número de conexiones.
Lo que funciona
- Siempre espera tus promises: una promise sin
awaites fire-and-forget — si rechaza y nada la espera, Node.js emiteunhandledRejection, que crashea el proceso desde Node 15. En funciones async, hazawaito.catch()a cada promise que crees. Promise.allpara trabajo independiente, awaits secuenciales para dependencias: si la tarea B necesita el resultado de A, deben correr en orden. Las llamadas independientes desperdician tiempo si las serializas —Promise.all,asyncio.gatheroCompletableFuture.allOflas corren en concurrente.- Timeout a cada llamada externa: una API que nunca responde cuelga la tarea para siempre. Envuelve las llamadas externas en un timeout con reintentos para que los fallos queden acotados y las latencias sean predecibles.
- Concurrencia estructurada antes que fire-and-forget: una tarea lanzada que sobrevive a su padre fuga memoria y compite en el apagado. Usa
TaskGroup, ámbitos conPromise.allSettledo tokens de cancelación explícitos para que cada tarea tenga un padre que la posea. - Perfila el event loop en vez de adivinar: en Node.js usa
perf_hooks.monitorEventLoopDelay()oclinic.js; en Python correasyncioconPYTHONASYNCIODEBUG=1. Si el trabajo CPU bloquea el loop, muévelo a un worker thread o a un pool de procesos.
Errores comunes
- Bloquear el event loop: un
fs.readFileSynco un loop CPU-pesado dentro de una función async detiene todas las demás peticiones del proceso. Usafs.promises.readFiley descarga el trabajo CPU a worker threads. - Callback hell sin async/await: cadenas
.then()muy anidadas son difíciles de leer y depurar. El JavaScript moderno debería usarasync/awaitpara todo menos el one-liner — código plano que se lee síncrono pero corre async. - Condiciones de carrera sobre estado mutable compartido: dos tareas incrementando un contador a través de boundaries de
awaitcompiten igual —awaites un punto de suspensión, no un lock. Usa operaciones atómicas, locks o paso de mensajes en vez de mutación compartida. - Ignorar el backpressure: aceptar trabajo más rápido de lo que puedes drenarlo hace crecer la memoria hasta que el OOM killer interviene. Un 503 rápido es mejor que un servidor caído.
Cuándo no usar este enfoque
- Tareas CPU-bound: async no ayuda cuando el cuello de botella es la CPU; usa worker threads o pools de procesos.
- Scripts secuenciales simples: una llamada HTTP, esperar, salir — un
requests.get()directo se lee más claro que el equivalente asyncio. - Deadlines de tiempo real duros: los schedulers async son cooperativos y no deterministas; mantenlos fuera del camino crítico de sistemas sensibles a deadlines.
- Embebidos con memoria muy limitada: cada tarea pendiente retiene un closure y una máquina de estado; miles de tareas suspendidas se acumulan.
- Codebases legacy síncronos: convertir a async implica tocar cada llamada de I/O de la cadena; el beneficio rara vez justifica la refactorización.
- Jobs batch de una sola petición: un job nocturno que llama a un endpoint y escribe una fila no gana nada con async.
Benchmarks de rendimiento
Tómalos como órdenes de magnitud, no como garantías — mide en tu propio stack y con tus tamaños de payload:
- Capacidad del event loop: un proceso Node.js en una VM chica (2 cores / 4 GB) puede mantener del orden de 10k conexiones keep-alive; el límite lo ponen los file descriptors y la memoria por conexión, no la CPU.
- asyncio vs hilos: para fan-out HTTP I/O-bound,
asyncioconaiohttpsuele sostener varias veces el throughput de un stack WSGI con hilos en un solo core, porque no hay stack por hilo ni contención del GIL mientras se espera. - Coste de goroutines: las goroutines arrancan con stacks de ~2 KB que crecen bajo demanda, así que cientos de miles caben en unos pocos cientos de MB.
- Coste de spawn: crear una tarea tokio cuesta decenas de nanosegundos; un hilo del SO cuesta microsegundos más una reserva de stack de escala MB.
- Virtual threads de Java: los virtual threads de JDK 21 viven en el heap, no en stacks del SO — un millón de ellos es realista donde un millón de platform threads agotaría el espacio de direcciones.
- Coste de cambio de contexto: un cambio de hilo del SO toma ~1–10 µs; un cambio de tarea async es una llamada a función, del orden de ~100–500 ns.
- Latencia bajo carga: los sistemas de event loop mantienen percentiles de cola más ajustados bajo carga de I/O porque no hay espera en cola del pool de hilos; la saturación de CPU es donde el modelo se cae.
- Presión de GC: cada tarea pendiente es una máquina de estado alojada en el heap. Una rotación alta de tareas se traduce en carga de GC — conviene vigilarlo en JVM y V8.
Estrategia de testing
- Unit test de funciones async en aislamiento: mockea el boundary de I/O y verifica valores de retorno; usa
pytest.mark.asyncio(oanyio) en Python y el soporte async de tu runner en JavaScript. - Integration test contra I/O real: levanta un servidor HTTP local y una base de datos; los bugs end-to-end se esconden justo donde terminan los mocks.
- Stress test con concurrencia realista: lanza 1.000+ tareas concurrentes y verifica que no haya deadlocks, fugas ni resultados incorrectos — las condiciones de carrera solo aparecen bajo presión de intercalación.
- Test de comportamiento de timeouts: verifica que las operaciones lentas disparen de verdad los timeouts de
asyncio.wait_foro los wrappers de deadline basados enPromise.race, y que el I/O subyacente se cancela en lugar de quedar abandonado. - Test de propagación de cancelación: cancela una tarea padre y verifica que todas las hijas se cancelen; en
asyncio, la cancelación llega comoCancelledErroren el siguienteawait. - Test de propagación de errores: los fallos de las hijas deben llegar al padre —
TaskGrouplos expone comoExceptionGroup,Promise.allrechaza ante el primer fallo. - Detección de carreras: corre los tests de Python con el modo debug de
asyncio(PYTHONASYNCIODEBUG=1) para detectar recursos sin cerrar y callbacks lentos; usa ThreadSanitizer donde haya hilos reales. - Load test con payloads de tamaño productivo: los payloads de juguete esconden problemas de backpressure y GC.
- Test de backpressure: envía peticiones más rápido de lo que el servicio las drena y verifica que descarte carga (503, colas acotadas) en lugar de comerse la memoria.
- Chaos testing del camino async: mata tareas a mitad de un
await, inyecta delay de red, corta conexiones — los handlers de cleanup y los bloquesfinallyson donde viven los bugs async.
Estimación de costos
- Dimensionado de servidores: las cargas async necesitan menos máquinas y más chicas — un servidor Node.js async manteniendo ~10k conexiones corre bien en una instancia de 2 cores / 4 GB, que cuesta en torno a $20–40/mes en los clouds habituales.
- Pools de conexiones: los drivers async habituales (
aiohttp,asyncpg,undici) son open source; el coste es operativo, no de licencias. - Coste de desarrollo: el código async suele llevar un 20–30 % más de tiempo en escribirse y depurarse que el equivalente síncrono — las rutas de error a través de puntos de suspensión son lo caro.
- Overhead de monitoreo: necesitas métricas de lag del event loop, profundidad de la cola de tareas y tasa de rejections que el monitoreo genérico de host no cubre.
- Ahorro de infraestructura: pasar de thread-per-request a async suele recortar la capacidad necesaria 3–5x en servicios I/O-bound.
- Memoria: una tarea async cuesta KBs; un hilo del SO cuesta un stack de escala MB. Con 10k unidades de trabajo concurrentes es la diferencia entre decenas de MB y decenas de GB.
- Coste operativo: menos partes móviles — sin tuning de pools de hilos ni debugging de contención de locks — a cambio de una clase distinta de bugs (tareas colgadas, promises sin await).
Monitoreo y observabilidad
- Lag del event loop: el delay entre un callback planificado y su ejecución; un lag sostenido por encima de ~50 ms indica que algo bloquea. Mídelo con
perf_hooks.monitorEventLoopDelay()en Node.js oclinic.jspara perfiles. - Profundidad de la cola de tareas: tareas pendientes creciendo de forma monótona significa que los productores superan a los consumidores — es un problema de backpressure, no de escalado.
- Conexiones activas: registra el conteo de conexiones concurrentes contra
ulimit -n. - Rejections y excepciones sin manejar: cuenta los eventos
unhandledRejectionen Node.js y las líneas “Task exception was never retrieved” en asyncio. - Pausas de GC: una rotación alta de tareas aloca muchos objetos pequeños; observa las pausas de GC junto con el crecimiento del heap.
- Memoria: RSS y heap — el crecimiento lento bajo carga constante es la firma de una tarea o un closure fugado.
- Percentiles de latencia: p50/p95/p99 por endpoint; un p99 que se abre con p50 plano suele significar que unas pocas rutas de código bloquean el loop.
Checklist de despliegue
- Límites de file descriptors configurados:
ulimit -n 65536oLimitNOFILE=65536en la unit de systemd - Pools de conexiones dimensionados para la concurrencia esperada — en async bastan pools chicos (aprox. 2× cores), no 50+
- Timeouts en todo el I/O: clientes HTTP, queries de base de datos, lecturas de caché; una banda sensata es 5–30 s
- Logging estructurado con request IDs para que las cadenas de llamadas async sigan siendo trazables
- Health checks que prueben que el event loop responde, no solo que el proceso vive
- Límites de memoria y comportamiento del OOM killer configurados — las tareas async son baratas, las colas sin límite no
- Apagado graceful: dejar de aceptar trabajo, drenar las tareas en vuelo durante 5–10 s y salir
Consideraciones de seguridad
- Inundación de tareas: un atacante que pueda disparar creación de tareas genera trabajo ilimitado; acota la concurrencia y aplica rate limiting en los puntos de entrada.
- Inyección de callbacks: si el input del usuario elige qué callback corre, elige qué código corre. Nunca dispaches sobre strings crudos del usuario.
- DoS por rejections sin manejar: en Node.js 15+ un rejection de promise sin manejar crashea el proceso. Adjunta
.catch()en cada promise o un handlerunhandledRejectiona nivel de proceso. - Bloqueo del event loop: una llamada síncrona (
fs.readFileSync,time.sleep,crypto.pbkdf2Sync) detiene todas las peticiones del proceso. Audita las dependencias en busca de I/O síncrono. - Estado compartido a través de awaits: los boundaries de
awaitpermiten intercalación; un read-modify-write repartido a través de unawaitcompite. - Timeouts que no cancelan: si el I/O subyacente ignora la cancelación, la tarea hace timeout pero la operación sigue consumiendo recursos — usa transports que respeten la cancelación.
- Fugas de memoria por closures: cada tarea captura su scope; tareas long-lived que retienen objetos grandes impiden el GC.
- Riesgo de supply chain: los drivers async (
aiohttp,tokio, Netty) han tenido CVEs; fija versiones y vigila los advisories. - DoS por clientes lentos: clientes estilo slowloris mantienen conexiones abiertas — configura timeouts de lectura y límites de conexiones por IP.
- Deserialización insegura:
await response.json()sobre un payload sin límite carga todo el body en memoria antes de validar; impone límites de tamaño primero. - Suplantación de coroutines: en Python, cualquier objeto que implemente
__await__puede recibirawait— no hagas await de objetos construidos desde input no confiable. - Agotamiento de file descriptors: cada conexión cuesta un FD; sin límites, una inundación de conexiones mata el proceso.
- Fuga de información en mensajes de error: los stack traces async son profundos y pueden exponer paths internos, query strings o credenciales — sanitiza lo que cruza el cable.
- Verificación TLS desactivada: algunos clientes HTTP traen defaults de TLS permisivos — verifica que la comprobación de certificados esté realmente activa.
- ReDoS en el event loop: una regex patológica corre en el hilo del loop y lo detiene todo; saca el matching del loop o usa
re2(node-re2) para matching en tiempo lineal. - Cancelación vs trabajo no idempotente: cancelar una tarea a mitad de operación (p. ej., un cobro) puede cobrar dos veces si la operación completó antes de que la cancelación llegara; haz los handlers idempotentes.
- Disciplina de
async with: saltarteasync withen sesiones y conexiones fuga recursos de forma invisible. - Bypass de backpressure: productor rápido + consumidor lento + cola sin límite = agotamiento de memoria.
- Manipulación de prioridad de tareas: un atacante que controle el timing de las tareas puede dejar sin recursos a las críticas.
- Timing attacks en autenticación: compara secretos con
secrets.compare_digest(Python) ocrypto.timingSafeEqual(Node.js), nunca con==, para que el scheduling no filtre prefijos de bytes. - Prototype pollution de Promise: si
Promise.prototypepuede modificarse, todo lo de abajo queda comprometido; hazObject.freezeen entornos con plugins hostiles. - Bypass de middleware async: un eslabón de middleware sin
awaitpuede saltarse — siempreawait next(). - Check-then-act en rate limiting: leer y actualizar un contador en awaits separados deja pasar peticiones concurrentes; haz la comprobación atómica.
- El apagado forzado salta el cleanup:
SIGKILLnunca ejecuta los handlers de cleanup async; confía enSIGTERMmás una ventana de drenaje. - Inanición de pools compartidos: consumidores que comparten un pool de conexiones sin cuotas permiten que un pico deje a los demás sin recursos.
- Logging síncrono en el loop: un logger bloqueante dentro de un handler async detiene el loop; usa logging buffered o async.
- Locks retenidos durante la cancelación: una coroutine cancelada mientras retiene un lock puede no liberarlo nunca; adquiere locks con
async withy timeout. - Inundación de microtasks: cadenas recursivas de
Promise.resolve().then()monopolizan la cola de microtasks y dejan sin recursos a los macrotasks.
Solución de problemas
- Carreras que solo aparecen bajo carga: protege el estado compartido con locks, atómicas o paso de mensajes; reproduce con stress tests dirigidos, no con unit tests.
- Deadlock entre workers: establece un orden global de adquisición de locks y mantén las secciones críticas libres de
await. - Saturación del pool de hilos (código CompletableFuture/executor): observa la longitud de la cola y la política de rechazo; agranda el pool solo si CPU y memoria lo permiten.
- Colas o mailboxes de actores creciendo sin límite: aplica backpressure, colas acotadas y load shedding aguas arriba.
- Tarea async que nunca termina: busca un
awaitolvidado, un rejection no manejado tragado aguas arriba o un loop que nunca cede al scheduler.
Lectura adicional
- MDN — JavaScript asíncrono y la referencia de Promise
- Documentación de
asyncioen Python y las notas de TaskGroup en What’s New in 3.11 - Javadoc de
CompletableFuturey JEP 444: Virtual Threads - Node.js — No bloquees el event loop
- TechEmpower Framework Benchmarks — números de throughput públicos y reproducibles si quieres comparar stacks en lugar de fiarte de órdenes de magnitud
Notas de producción
- Despliega gradualmente con canary o blue-green — los bugs del event loop solo aparecen bajo concurrencia real.
- Configura alertas de lag del event loop, tasa de rejections no manejadas y latencia p99 antes del rollout, no después.
- Mantén un rollback documentado en el runbook; volver de async a síncrono es la dirección dolorosa.
- Propaga correlation IDs de punta a punta — los saltos async de una petición son invisibles sin ellos.
Puntos clave
- Async/await compra concurrencia en un solo hilo; es una herramienta de I/O, no de CPU.
- Acota la concurrencia (semáforos, pools) y pon timeout a cada llamada externa — ambos son innegociables en producción.
- Prefiere concurrencia estructurada (
TaskGroup, combinadores de promises con scope) para que las tareas tengan un dueño en vez de quedar huérfanas. - Mide el event loop en sí — lag, profundidad de cola, rejections — porque sus modos de fallo no aparecen en los logs de peticiones.
Modos de fallo en producción
- Desplegar un fan-out de
Promise.all/gathersin límite que se derrite con tráfico real. - Hacer timeout a la tarea pero no al I/O subyacente, de modo que el recurso sigue fugando en segundo plano.
- Hacer await a trabajo disparado por el usuario sin rate limiting — un vector de denegación de servicio gratis.
- Retener un lock o una transacción a través de un
await, que bloquea el lock durante toda la suspensión. - Tareas fire-and-forget que mueren en silencio en el apagado y dejan estado a medio escribir.
- Asumir que async elimina las carreras: el estado compartido a través de un boundary de
awaitsigue compitiendo. - Desplegar sin métricas del event loop — vuelas a ciegas sobre el único recurso que importa.
- Vigilar el p50 mientras el p99 arde.
Preguntas frecuentes
¿Async es siempre más rápido que síncrono?
Solo para cargas I/O-bound. En tareas CPU-bound (procesamiento de imágenes, inferencia de ML), async no aporta nada porque la CPU ya está saturada — usa hilos, procesos o workers dedicados para el paralelismo de CPU.
¿Cuántas peticiones concurrentes puede manejar un solo proceso de Node.js?
Miles, acotadas por memoria y file descriptors, no por CPU. El event loop ejecuta una operación a la vez, pero la mayor parte del tiempo cada operación está esperando I/O. Un servidor Node.js típico mantiene 5.000–10.000 conexiones concurrentes.
¿Cuál es la diferencia entre concurrencia y paralelismo?
La concurrencia es intercalar tareas en un solo core (async/await). El paralelismo es ejecutar tareas simultáneamente en varios cores (hilos/procesos). Async te da concurrencia; multiprocessing te da paralelismo. Los sistemas de alto throughput suelen usar ambos.
¿Debería usar hilos o async en Python?
asyncio para trabajo I/O-bound con muchas conexiones, threading para I/O a través de librerías que bloquean y no soportan async, multiprocessing para trabajo CPU-bound que necesita evitar el GIL. Para servidores web y clientes de API, asyncio suele ser el default correcto.
¿Qué pasa si olvido hacer await a una promise?
La promise corre igual — await no la inicia, solo espera su resultado. Una promise sin await es fire-and-forget: si rechaza, el error cae en unhandledRejection, que crashea el proceso en Node.js 15+.
¿Cómo limito la concurrencia con asyncio?
Envuelve el trabajo en un asyncio.Semaphore(N) y adquiérelo dentro de cada tarea — combínalo con TaskGroup si además quieres propagación de fallos. No existe un gather(limit=N) incorporado.
¿Por qué mi código async sigue siendo lento?
Normalmente una de estas tres causas: una llamada síncrona corriendo en el loop (requests, time.sleep, una regex pesada), un fan-out sin límite que satura el servicio externo, o trabajo CPU-bound que necesita un pool de procesos en lugar de coroutines. Mide el lag del event loop primero.
Código companion: El repo companion de async-patterns tiene los tres ejemplos como archivos ejecutables, incluido un fetcher con semáforo que puedes apuntar a cualquier lista de URLs.
Recursos Relacionados
Colecciones Thread-Safe: Blocking Queues y Concurrent Maps
Cómo compartir colecciones entre hilos de forma segura usando estructuras concurrentes: colas, mapas, listas y contadores atómicos en Java, Python y C++.
RecipeCoordinar Acceso Compartido con Locks, Mutexes y Semáforos
Cómo prevenir condiciones de carrera en programas concurrentes usando mutexes, read-write locks, semáforos y operaciones atómicas en Java, Python y C++.
RecipeGestionar Trabajo Concurrente con Thread Pools y Executors
Cómo gestionar worker threads eficientemente usando thread pools, executors y políticas de rechazo en Java, Python y C# para cargas CPU-bound e I/O-bound.
RecipeCoordinar Tareas Concurrentes con Communicating
Cómo estructurar programas concurrentes usando channels, select statements y goroutines para comunicación segura sin estado mutable compartido en Go, Rust y JavaScript.
RecipeDiseñar Sistemas Event-Driven con Event Buses y Brokers
Cómo diseñar servicios débilmente acoplados con eventos, event buses y message brokers: patrones pub/sub y event sourcing para comunicación asíncrona.
DocRunbook de cancelación asíncrona — Python, JS, Go, Java
Runbook para cancelar tareas asíncronas largas sin fugas: señales cooperativas, presupuesto de limpieza, propagación y verificación en Python, JS, Go, Java.