Nivel 4: Creemos un simulador de procesamiento de pedidos concurrente con Golang

Gorrutinas y canales


NIVEL 4: Gorrutinas y Canales - Creando un Simulador de Procesamiento de Pedidos Concurrente en Go

¡Hola, Gophers! 👋 Bienvenidos al cuarto nivel de nuestra serie de tutoriales de Go. Hoy vamos a sumergirnos en uno de los aspectos más emocionantes y poderosos de Go: la concurrencia. Vamos a crear un Simulador de Procesamiento de Pedidos Concurrente que no solo te enseñará sobre gorrutinas y canales, sino que también te mostrará cómo aplicar estos conceptos en un escenario del mundo real. ¿Estás listo para llevar tus habilidades de Go al siguiente nivel? ¡Vamos allá!

🎯 ¿Qué vamos a construir?

Vamos a crear un sistema que simule el procesamiento de múltiples pedidos en una tienda en línea. Nuestro programa hará lo siguiente:

  1. Generar pedidos aleatoriamente
  2. Verificar el inventario para cada pedido
  3. Procesar el pago para pedidos válidos
  4. Preparar el envío para pedidos pagados

Y lo mejor de todo, ¡haremos que cada una de estas etapas se ejecute concurrentemente usando gorrutinas y se comuniquen entre sí mediante canales!

🧠 Conceptos clave que aprenderemos

  • Gorrutinas: las unidades básicas de ejecución concurrente en Go
  • Canales: el mecanismo de comunicación y sincronización entre gorrutinas
  • Select: para manejar múltiples operaciones de canal
  • Patrones de concurrencia comunes en Go
  • Diferencia entre concurrencia y paralelismo
  • Uso seguro de generadores de números aleatorios en entornos concurrentes

🛠 Manos a la obra

Paso 1: Configuración inicial

Primero, vamos a crear la estructura básica de nuestro programa:

package main import ( "fmt" "math/rand" "time" ) type Order struct { ID int Item string Quantity int } func main() { fmt.Println("Simulador de Procesamiento de Pedidos Concurrente") // Crear una nueva fuente de aleatoriedad source := rand.NewSource(time.Now().UnixNano()) r := rand.New(source) // Aquí irá nuestro código principal }

Analicemos los imports y su propósito:

  • fmt: Para imprimir en la consola. Lo usaremos para mostrar el progreso de nuestros pedidos.
  • math/rand: Generará números aleatorios para simular diferentes aspectos de nuestro proceso.
  • time: Lo usaremos para añadir retrasos realistas y para establecer la semilla del generador de números aleatorios.

La estructura Order representa un pedido en nuestro sistema. Cada pedido tiene un ID único, el nombre del item y la cantidad solicitada.

En la función main, en lugar de usar rand.Seed(), ahora creamos un generador de números aleatorios local. Esto sigue las mejores prácticas actuales de Go y es más seguro para uso concurrente.

Paso 2: Entendiendo canales y gorrutinas

Antes de sumergirnos en la generación de pedidos, es crucial entender los conceptos de canales y gorrutinas, así como la diferencia entre paralelismo y concurrencia.

Canales en Go

Los canales son un tipo de dato en Go que actúan como conductos a través de los cuales puedes enviar y recibir valores. Son fundamentales para la comunicación entre gorrutinas.

// Crear un canal ch := make(chan int) // Enviar un valor al canal ch <- 42 // Recibir un valor del canal value := <-ch

Los canales pueden ser buffereados (con capacidad) o no buffereados. En nuestro ejemplo, usaremos canales no buffereados para sincronizar nuestras gorrutinas naturalmente.

Concurrencia vs Paralelismo

  • Concurrencia se refiere a la composición de procesos independientes que se ejecutan y comunican simultánea y coordinadamente. No implica necesariamente ejecución simultánea.
  • Paralelismo implica la ejecución real y simultánea de tareas, generalmente en múltiples núcleos de CPU.

Go está diseñado para la concurrencia y puede lograr paralelismo en sistemas con múltiples núcleos, pero su modelo principal es concurrente.

Gorrutinas

Las gorrutinas son funciones o métodos que se ejecutan concurrentemente con otras funciones o métodos. Son más livianas que los hilos del sistema operativo y son gestionadas por el runtime de Go.

Para iniciar una gorrutina, simplemente usamos la palabra clave go antes de una llamada a función:

go funcionConcurrente()

Ahora, veamos cómo aplicamos estos conceptos en nuestro generador de pedidos.

Paso 2 (continuación): Generando pedidos

Vamos a crear una gorrutina que genere pedidos aleatoriamente:

func generateOrders(orderChan chan<- Order, r *rand.Rand) { for i := 1; ; i++ { order := Order{ ID: i, Item: fmt.Sprintf("Item-%d", r.Intn(100)), Quantity: r.Intn(10) + 1, } orderChan <- order time.Sleep(time.Millisecond * time.Duration(r.Intn(500))) } }

Esta función se ejecutará como una gorrutina, generando pedidos continuamente. Observa el uso de chan<- Order, que indica un canal de solo escritura para Order. Esto es parte del sistema de tipos de canales en Go, que ayuda a prevenir errores de uso.

Analicemos la estructura del for:

  1. for i := 1; ; i++ es un bucle infinito con un contador.
  2. i := 1 inicializa el contador.
  3. El segundo campo está vacío, lo que significa que no hay condición de parada.
  4. i++ incrementa el contador en cada iteración.

Este bucle continuará indefinidamente, generando nuevos pedidos con IDs secuenciales.

Paso 3: Verificando el inventario

Ahora, crearemos una gorrutina para verificar el inventario:

func checkInventory(orderChan <-chan Order, validOrderChan chan<- Order, r *rand.Rand) { for order := range orderChan { if r.Float32() < 0.8 { // 80% de probabilidad de tener inventario validOrderChan <- order fmt.Printf("Inventario verificado para el pedido %d\n", order.ID) } else { fmt.Printf("Inventario insuficiente para el pedido %d\n", order.ID) } time.Sleep(time.Millisecond * time.Duration(r.Intn(200))) } }

Analicemos esta función en detalle:

  1. Parámetros de la función:
    • orderChan <-chan Order: Canal de solo lectura de tipo Order.
    • validOrderChan chan<- Order: Canal de solo escritura de tipo Order.
    • r *rand.Rand: Generador de números aleatorios local.
  2. El uso de canales direccionales (<-chan y chan<-) ayuda a prevenir errores y clarifica la intención de la función.
  3. El bucle for order := range orderChan itera sobre el canal, procesando cada orden recibida.
  4. Usamos r.Float32() < 0.8 para simular una verificación de inventario con un 80% de probabilidad de éxito.

El uso de gorrutinas aquí permite procesar múltiples verificaciones de inventario concurrentemente, mejorando la eficiencia del sistema.

Paso 4: Procesando pagos

Siguiente, una gorrutina para procesar pagos:

func processPayment(validOrderChan <-chan Order, paidOrderChan chan<- Order, r *rand.Rand) { for order := range validOrderChan { if r.Float32() < 0.9 { // 90% de probabilidad de pago exitoso paidOrderChan <- order fmt.Printf("Pago procesado para el pedido %d\n", order.ID) } else { fmt.Printf("Fallo en el pago para el pedido %d\n", order.ID) } time.Sleep(time.Millisecond * time.Duration(r.Intn(300))) } }

Esta función simula el procesamiento de pagos con una probabilidad de éxito del 90%. El uso de gorrutinas aquí permite manejar múltiples transacciones de pago simultáneamente, lo cual es crucial en sistemas de comercio electrónico del mundo real donde la latencia de las transacciones puede variar.

Paso 5: Preparando envíos

Finalmente, una gorrutina para preparar envíos:

func prepareShipment(paidOrderChan <-chan Order, r *rand.Rand) { for order := range paidOrderChan { fmt.Printf("Envío preparado para el pedido %d\n", order.ID) time.Sleep(time.Millisecond * time.Duration(r.Intn(400))) } }

Esta función simula la preparación de envíos para los pedidos pagados. En un sistema real, aquí se realizarían acciones como asignar números de seguimiento, generar etiquetas de envío, y actualizar el inventario.

Paso 6: Juntando todo

Ahora, vamos a unir todas estas partes en nuestra función main:

func main() { fmt.Println("Simulador de Procesamiento de Pedidos Concurrente") // Crear una nueva fuente de aleatoriedad source := rand.NewSource(time.Now().UnixNano()) r := rand.New(source) orderChan := make(chan Order) validOrderChan := make(chan Order) paidOrderChan := make(chan Order) go generateOrders(orderChan, r) go checkInventory(orderChan, validOrderChan, r) go processPayment(validOrderChan, paidOrderChan, r) go prepareShipment(paidOrderChan, r) // Dejamos que el programa corra por un tiempo time.Sleep(time.Second * 30) }

En esta función main, creamos los canales necesarios y lanzamos nuestras gorrutinas. Observa cómo cada gorrutina se comunica con la siguiente a través de los canales, formando un pipeline de procesamiento.

Observa cómo pasamos el generador de números aleatorios r a cada una de nuestras gorrutinas. Esto asegura que cada gorrutina tenga su propio generador de números aleatorios, evitando posibles problemas de concurrencia.

🚀 Ejecutando nuestro simulador

Para ejecutar nuestro simulador, guarda todo el código en un archivo main.go y ejecuta:

go run main.go

Verás cómo los pedidos se procesan concurrentemente a través de las diferentes etapas.

Source Code

https://github.com/Gognition/gognition-nivel4-simulador-pedidos-concurrentes.git

🎓 ¿Qué hemos aprendido?

  • Cómo crear y usar gorrutinas para operaciones concurrentes
  • Cómo utilizar canales para la comunicación entre gorrutinas
  • La diferencia entre concurrencia y paralelismo
  • Cómo simular procesos del mundo real usando concurrencia
  • La importancia de la sincronización en operaciones concurrentes
  • Patrones básicos de pipeline usando gorrutinas y canales
  • Cómo usar un generador de números aleatorios local para mejorar la seguridad en concurrencia

🛠 Reto para ti

¿Qué tal si expandimos nuestro simulador? Aquí tienes algunas ideas:

  1. Implementa un sistema de logging concurrente para registrar todas las operaciones.
  2. Añade timeouts para simular fallos en el proceso.
  3. Implementa un mecanismo para cancelar pedidos en proceso.
  4. Crea un panel de control que muestre estadísticas en tiempo real del procesamiento de pedidos.
  5. Modifica el sistema para usar un número configurable de trabajadores en cada etapa.

Conclusión

¡Felicidades! Has creado un simulador de procesamiento de pedidos concurrente y has dado tus primeros pasos en el fascinante mundo de la concurrencia en Go. Este proyecto te ha introducido a conceptos fundamentales como gorrutinas, canales, y patrones básicos de concurrencia.

Recuerda, la concurrencia es una herramienta poderosa, pero también puede ser compleja. Puede llevar tiempo dominar todos sus aspectos, pero con práctica y experimentación, podrás crear sistemas Go altamente eficientes y escalables.

¿Preguntas? ¿Ideas para mejorar el simulador? ¡Déjalas en los comentarios abajo! Y no olvides compartir tu versión mejorada del simulador con la comunidad.

Hasta la próxima, ¡y que tus gorrutinas siempre se sincronicen! 🐹💻

Comentarios

Formulario de contacto

Enviar