Concurrencia en Producción: Patrones Más Allá de "Hello Goroutine"
Una go func() en un tutorial demuestra que el runtime funciona.
Busca en todas las páginas de la documentación
Una go func() en un tutorial demuestra que el runtime funciona.
Un servicio de producción debe limitar el paralelismo, propagar la cancelación, descargar carga bajo presión y apagarse sin dejar goroutines bloqueadas para siempre.
Esta página es el ancla conceptual para Concurrencia Avanzada.
Fundamentos de Concurrencia Avanzada recopila fragmentos ejecutables; artículos hermanos cubren pools de workers, pipelines, limitación de velocidad, errgroup, atómicos, prevención de fugas y hábitos de revisión.
Piensa en un servicio Go como un controlador de tráfico para el trabajo.
Las solicitudes llegan más rápido de lo que una base de datos puede responder si cada solicitud genera su propio árbol de goroutines ilimitado.
El código de producción introduce capacidad: un número fijo de workers, un semáforo que limita las llamadas en curso o un canal con búfer que bloquea a los productores cuando los consumidores se quedan atrás.
La cancelación es el segundo pilar.
context.Context transmite una señal de detención desde el manejador HTTP a todas las llamadas descendentes.
Cuando el cliente se desconecta o expira un plazo, el trabajo en curso debe salir rápidamente en lugar de mantener las conexiones abiertas.
El tercer pilar es la composición.
Patrones simples se combinan en sistemas más grandes:
Estas no son religiones en competencia.
Un manejador único puede usar un semáforo para HTTP saliente, errgroup para búsquedas paralelas y un pool de workers para reintentos en segundo plano.
Solicitud del cliente
|
v
Manejador HTTP (ctx de r.Context())
|
+----+----+
| |
v v
errgroup llamadas
paralelas descendentes
búsquedas limitadas por semáforo
| |
+----+----+
v
fusionar / responder
|
en ctx.Done() -> dejar de generar, drenar o abandonar
Los pools de workers limitan el paralelismo intensivo en CPU o I/O a un número que tú elijas, a menudo vinculado al tamaño del pool de conexiones o al número de CPUs.
Los trabajos entran en un canal; los workers iteran sobre él hasta que el canal se cierra o el contexto se cancela.
Los pipelines separan las responsabilidades: el análisis, la validación, el enriquecimiento y la persistencia pueden ejecutarse en su propia etapa con búferes limitados entre ellas.
La contrapresión aparece naturalmente cuando una etapa ascendente se bloquea en el envío porque el búfer descendente está lleno.
errgroup coordina un lote de goroutines con cancelación compartida: el primer error cancela a los hermanos y devuelve un error combinado al llamador.
Emparejarlo con context.WithCancel derivado del contexto de la solicitud.
La limitación de velocidad (cubo de tokens, golang.org/x/time/rate, o un semáforo ponderado) protege los recursos compartidos.
El apagado elegante escucha SIGTERM, deja de aceptar nuevo trabajo, espera las solicitudes en curso con un tiempo de espera, y luego cierra los canales de workers para que las goroutines salgan.
| Patrón | Fortaleza | Debilidad | Mejor Ajuste |
|---|---|---|---|
go ilimitado por tarea | Código más simple | Sin contrapresión; riesgo de OOM | Solo prototipos |
| Pool de workers | Paralelismo predecible | Ajuste del tamaño de la cola | Pools de CPU/IO, procesadores de trabajos |
| Etapas de pipeline | Flujo de datos claro | Más partes móviles | ETL, procesamiento de logs |
| errgroup | Cancelación al primer error | Menos control sobre resultados parciales | Búsquedas paralelas |
| Semáforo / limitador de velocidad | Protege el sistema descendente | Añade latencia bajo carga | Gateways de DB/API |
Los servidores HTTP en net/http ya ejecutan cada solicitud en su propia goroutine.
El trabajo avanzado está dentro del manejador: limita las llamadas concurrentes a Postgres, Redis o una API de un socio.
Haz coincidir el tamaño del pool con SetMaxOpenConns en database/sql para no poner en cola goroutines detrás de un pool agotado.
Para RPC de streaming o WebSockets, una goroutine de larga duración por conexión es normal.
Aún así, limita el trabajo auxiliar (actualización en segundo plano, enriquecimiento fan-out) con semáforos.
La observabilidad cierra el bucle: rastrea el recuento de goroutines (runtime.NumGoroutine), las solicitudes en curso, la profundidad de la cola y el tiempo bloqueado en semáforos.
Los picos sin crecimiento de tráfico a menudo señalan una fuga o un bloqueo.
Ejecuta pruebas de carga con -race en CI para paquetes que mutan estado compartido.
Kubernetes envía SIGTERM antes que SIGKILL.
Tu main debe llamar a http.Server.Shutdown con un plazo, cancelar un contexto raíz, cerrar canales de trabajos y Wait() en grupos de workers.
Documenta la propiedad: quién cierra qué canal, quién espera a quién.
La integración con contexto es obligatoria para los manejadores de producción.
Pasa r.Context() a http.NewRequestWithContext, llamadas gRPC y db.QueryContext.
Consulta Paquete context para el modelo de cancelación.
sync.Mutex o atómicos; los canales son para pasar la propiedad, no cada byte compartido.sync.WaitGroup sigue siendo correcto cuando los errores se manejan por goroutine y no necesitas cancelación automática al ocurrir un error.ctx.Done(); el cierre por sí solo no desbloquea envíos que esperan en un búfer lleno.Un semáforo limitado o un pool de workers pequeño en I/O saliente.
Es el cambio más pequeño que evita goroutines ilimitadas cuando el tráfico aumenta.
GOMAXPROCS limita los hilos del sistema operativo que ejecutan código Go.
Los pools de workers limitan las tareas a nivel de aplicación, a menudo por debajo del número de CPUs para I/O o coincidiendo con los límites de conexión externos.
Canales cuando los productores y consumidores son goroutines que pasan elementos de trabajo.
Un mutex más un slice cuando necesites prioridad indexada, inspección o lógica de vaciado de una sola goroutine.
Proporciona una goroutine por solicitud, no un pool para tu trabajo interno.
Aún limitas las llamadas paralelas a la base de datos o al cliente HTTP dentro de los manejadores.
Un consumidor lento causa un envío bloqueante en un canal o una Acquire fallida en un semáforo, lo que ralentiza a los productores en lugar de almacenar en búfer trabajo ilimitado en memoria.
errgroup devuelve el primer error y puede cancelar un contexto derivado.
WaitGroup solo espera; el manejo de errores es manual.
No, usa un contexto separado cancelado al apagar el proceso.
El contexto de la solicitud termina cuando el cliente se desconecta, lo cual es incorrecto para registros de auditoría de "disparar y olvidar", a menos que los desvincules explícitamente.
Inicia workers, cancela el contexto, cierra las entradas, afirma que Wait() retorna dentro de un tiempo de espera y que el recuento de goroutines vuelve a la línea base.
Solo cuando el trabajo es independiente y la capacidad descendente soporta N llamadas paralelas.
De lo contrario, fan-out amplifica el estrangulamiento y los errores.
runtime.NumGoroutine en aumento monotónico después de que la carga se detiene, además de un aumento en los recuentos de heap y FDs abiertos.
Sí, una etapa de pipeline puede usar errgroup internamente para pasos paralelos dentro de una etapa.
Ejecuta go test -race en paquetes con estado mutable compartido.
No detecta bloqueos ni carreras lógicas en canales.
Versiones de Stack: Esta página fue escrita para Go 1.26.x (predeterminado GC Green Tea, go fix modernizers - verificar parche en la compilación), chi (última versión - verificar en la compilación), gin (última versión - verificar en la compilación), echo (última versión - verificar en la compilación), google.golang.org/grpc (última versión - verificar en la compilación), sigs.k8s.io/controller-runtime (última versión - verificar en la compilación), kubebuilder (última versión - verificar en la compilación), tinygo (última versión - verificar objetivos de placa en la compilación), wazero (última versión - verificar en la compilación) y golangci-lint (última versión - verificar conjunto de linters en la compilación).
Revisado por Chris St. John·Última actualización: 16 jul 2026