Cache concurrente en Go para cálculos intensivos de Fibonacci

Clase 7 de 19Curso de Go Avanzado: Concurrencia y Patrones de Diseño

Resumen

¿Cómo resolver problemas de concurrencia en un sistema de caché para la serie de Fibonacci?

La implementación de un sistema de caché eficiente para la serie de Fibonacci puede parecer simple en escenarios básicos, pero se vuelve un desafío cuando se introducen procesos concurrentes. Imagínate una situación donde varios procesos simultáneos solicitan el cálculo de valores de Fibonacci para números grandes que, de no estar bien gestionados, podrían sobrecargar tu sistema. En este contexto, es esencial desarrollar un sistema que no solo gestione la caché eficientemente, sino que también identifique y gestione las operaciones que están en progreso y aquellas que ya están listas para entrega. En esta guía, veremos cómo implementar un sistema de caché con manejo de procesos concurrentes, tomando como referencia el lenguaje de programación Go.

¿Cómo simular un cálculo intensivo de Fibonacci?

Para simular un cálculo intensivo, se puede emplear una función que representa un tiempo de ejecución prolongado. La función Fibonacci no solo debe calcular el valor, sino también emular tiempo de procesamiento significativo:

func calcularFibonacci(n int) int {
    fmt.Printf("Calculando Fibonacci costoso para %d\n", n)
    time.Sleep(5 * time.Second) // Simula un cálculo largo
    return n
}

¿Cómo definir estructuras y mapas para gestionar trabajos en proceso?

Para manejar múltiples procesos concurrentes, es crucial definir estructuras y mapas que gestionen qué trabajos se están procesando actualmente y cuáles están esperando una respuesta:

type Servicio struct {
    enProgreso map[int]bool
    enEspera   map[int][]chan int
    mu         sync.RWMutex // candado para evitar condiciones de carrera
}
  • enProgreso: Mapa que indica si un trabajo está en proceso.
  • enEspera: Mapa que almacena canales de respuesta para trabajos que esperan resultados.

¿Cómo implementar la función de trabajo para gestionar procesos concurrentes?

El método trabajar se encarga de manejar las operaciones concurrentes. Comprueba si un trabajo está en progreso y actúa consecuentemente:

func (s *Servicio) trabajar(trabajo int) {
    s.mu.RLock()
    if s.enProgreso[trabajo] {
        // Si está en progreso, agrega el trabajador al mapa de espera
        ch := make(chan int, 1)
        s.mu.RUnlock()
        
        // Operaciones que involucran bloqueo total
        s.mu.Lock()
        s.enEspera[trabajo] = append(s.enEspera[trabajo], ch)
        s.mu.Unlock()

        // Espera la respuesta
        respuesta := <-ch
        fmt.Printf("Respuesta recibida: %d\n", respuesta)
        return
    }
    s.mu.RUnlock()

    // Si no está en progreso, calcúlelo y notifique a los trabajadores
    s.mu.Lock()
    s.enProgreso[trabajo] = true
    s.mu.Unlock()

    fmt.Printf("Calculando Fibonacci para %d\n", trabajo)
    resultado := calcularFibonacci(trabajo)

    s.mu.Lock()
    for _, ch := range s.enEspera[trabajo] {
        ch <- resultado
        close(ch)
    }
    s.enProgreso[trabajo] = false
    s.enEspera[trabajo] = nil
    s.mu.Unlock()
}

¿Cómo instanciar el servicio y gestionar trabajos concurrentemente?

Para poner a prueba nuestro sistema, definimos un conjunto de trabajos y los procesamos concurrentemente utilizando sync.WaitGroup:

func main() {
    servicio := &Servicio{
        enProgreso: make(map[int]bool),
        enEspera:   make(map[int][]chan int),
    }

    trabajos := []int{3, 4, 5, 5, 4, 8, 8, 8}
    wg := &sync.WaitGroup{}
    wg.Add(len(trabajos))

    for _, trabajo := range trabajos {
        go func(trabajo int) {
            defer wg.Done()
            servicio.trabajar(trabajo)
        }(trabajo)
    }
    
    wg.Wait()
}

Con esta implementación, hemos creado un sistema de caché que maneja de forma efectiva procesos concurrentes al gestionar trabajos en progreso y esperando resultados. Te animo a implementar tus propias mejoras y explorar combinaciones con otras técnicas de caché para crear una solución aún más robusta. ¡Sigue avanzando y experimentando con nuevas ideas!