# Patrón Fan-Out/Fan-In en Go: Procesamiento paralelo flexible de datos
El patrón Fan-Out/Fan-In ofrece una forma potente en Go para distribuir tareas entre goroutines y luego agregar los resultados. A diferencia del patrón Worker Pool, proporciona mayor flexibilidad mediante tuberías de procesamiento individuales para cada trabajador, lo que resulta especialmente útil para cadenas complejas de transformación de datos.
Principios fundamentales del patrón
El patrón se divide en dos fases clave. La fase Fan-Out distribuye los datos de entrada entre múltiples trabajadores (goroutines). Cada trabajador recibe su propia tarea, la procesa de forma independiente y envía el resultado a su canal dedicado. La fase Fan-In recopila luego los resultados de todos estos canales individuales en un único canal de salida para el procesamiento downstream.
La principal diferencia con el Worker Pool radica en la arquitectura de canales:
- Worker Pool: Todos los trabajadores suelen leer tareas de un canal compartido y escriben resultados en otro canal compartido.
- Fan-Out/Fan-In: Cada trabajador tiene su propio canal de salida, lo que requiere un paso adicional de fusión.
Este enfoque añade algo de sobrecarga debido a las goroutines y canales extra para la fusión, pero brilla en arquitecturas de tuberías donde las etapas posteriores necesitan flujos de datos separados.
Implementación práctica
Veamos una implementación práctica que se diferencia deliberadamente de un Worker Pool clásico para resaltar las características clave.
Función del trabajador
En esta configuración, el trabajador es una función que lanza una goroutine y devuelve un canal de resultados. Esto difiere de pasar un canal como parámetro.
func worker(x int, workerID int) chan int {
out := make(chan int)
go func() {
defer close(out)
fmt.Printf("worker %d, job %d\n", workerID, x)
out <- x * x
}()
return out
}
Detalles clave de implementación:
- La goroutine cierra su canal
outcondeferdespués de enviar el resultado. - Para la demo, el trabajo es trivial: elevar un número al cuadrado.
- En código de producción, refinarías esto: gestionar el ciclo de vida con contextos para apagados graciosos y timeouts.
Fase Fan-Out (Distribución)
El distribuidor (productor) lanza un trabajador por cada elemento de datos de entrada. Los canales devueltos por los trabajadores se almacenan en un slice para la fusión posterior.
for i, x := range data {
chnls = append(chnls, worker(x, i+1))
}
Característica clave: El número de trabajadores escala dinámicamente con el conteo de tareas, lo que no es obligatorio pero muestra la flexibilidad del patrón. En apps reales, usualmente limitarías el número de trabajadores concurrentes.
Fase Fan-In y función de fusión
La función merge se encarga del trabajo crucial de combinar datos de múltiples canales en uno solo. Hace lo siguiente:
- Crea un canal de salida único sin buffer
out. - Por cada canal de entrada, lanza una goroutine que lee datos y los reenvía a
out. - En una goroutine separada, espera a que todas las goroutines lectoras terminen usando
sync.WaitGroupy luego cierraout.
func merge(chnls ...chan int) <-chan int {
out := make(chan int)
var wg sync.WaitGroup
for _, ch := range chnls {
wg.Add(1)
go func(c chan int) {
defer wg.Done()
for x := range c {
out <- x
}
}(ch)
}
go func() {
wg.Wait()
close(out)
}()
return out
}
Nota técnica: La espera (wg.Wait()) se ejecuta en una goroutine separada. Hacerlo en el flujo principal podría causar un deadlock si el consumidor no ha empezado a leer de out, bloqueando las escrituras.
Consumidor
El consumidor lee resultados del canal único fusionado. El bucle for range sale automáticamente cuando merge cierra out.
for x := range out {
results = append(results, x)
}
Lecciones clave
- Flexibilidad arquitectónica: Tuberías individuales por trabajador facilitan insertar etapas extra de procesamiento de datos.
- Escalado dinámico: Teóricamente soporta un trabajador por tarea, aunque en la práctica controlarías la concurrencia.
- Sobrecarga: El patrón usa recursos extra (canales, goroutines) para la fusión en comparación con enfoques más simples.
- Gestión del ciclo de vida: Esta demo omite contextos, válido para ejemplos pero esencial en producción para cancelaciones y timeouts.
- Legibilidad del código: Canales sin buffer y bucles
for rangemantienen todo conciso y claro.
Recomendaciones para producción
Este código de demo es para aprender. Para uso real, realiza estas mejoras:
- Añade contexto (context.Context): Pásalo a trabajadores y
mergepara manejar cancelaciones, plazos y timeouts. - Limita la concurrencia: Goroutines ilimitadas (una por tarea) pueden agotar recursos. Usa semáforos o un pool de trabajadores de tamaño fijo en Fan-Out.
- Manejo de errores: Diseña para propagar errores de trabajadores al consumidor, como un canal de errores dedicado o struct de resultados.
- Canales con buffer: Pueden mejorar el rendimiento reduciendo bloqueos en algunos casos, pero prueba con cuidado.
El patrón Fan-Out/Fan-In es una herramienta poderosa en el kit de todo desarrollador Go para construir tuberías de procesamiento de datos eficientes y escalables, especialmente cuando necesitas flexibilidad en la gestión de flujos de datos entre etapas.
— Editorial Team
Aún no hay comentarios.