Concurrencia en Go: Uso de Goroutines y WaitGroups
Resumen
¿Cómo funcionan las Goroutines en Go?
Las Goroutines son una característica fundamental de Go para gestionar la concurrencia. Permiten ejecutar funciones de forma concurrente dentro del mismo programa. Básicamente, una Goroutine es una función que se ejecuta en paralelo con otras Goroutines en el mismo espacio de memoria. Esto puede mejorar la eficiencia del programa si se utiliza correctamente.
Para activar una Goroutine, simplemente se utiliza la palabra clave go antes de una llamada a función. Esto indica al programa que debe ejecutarse de manera concurrente. Sin embargo, puede haber problemas si la función main finaliza antes que las otras Goroutines terminen de ejecutar, como no imprimir algún texto que esperamos.
¿Cómo podemos coordinar las Goroutines con WaitGroup?
El paquete sync en Go proporciona la estructura WaitGroup, que se utiliza para coordinar la ejecución de Goroutines. Un WaitGroup permite programar la espera para que una función main no termine hasta que todas las Goroutines hayan concluido.
Veamos cómo se implementa:
package main
import("fmt""sync")funcimprimirTexto(texto string, wg *sync.WaitGroup){defer wg.Done() fmt.Println(texto)}funcmain(){var wg sync.WaitGroup
wg.Add(1)goimprimirTexto("Hello",&wg) wg.Add(1)goimprimirTexto("World",&wg) wg.Wait()}
Aquí, wg.Add(1) se utiliza para indicar que una nueva Goroutine se ha añadido al grupo que el WaitGroup está esperando. defer wg.Done() informa que la Goroutine ha concluido, y wg.Wait() en el main asegura que el programa espera la terminación de todas las Goroutines añadidas.
Uso de funciones anónimas con Goroutines y ¿cómo pueden recibir parámetros?
Las funciones anónimas son funciones que se definen en su lugar de uso, y son comunes cuando se desarrollan aplicaciones concurrentes con Goroutines en Go. Puedes definir y ejecutar una función anónima cuando desees realizar una tarea pequeña pero paralela.
package main
import("fmt""time")funcmain(){gofunc(texto string){ fmt.Println(texto)}("Adiós") time.Sleep(1* time.Second)}
En este ejemplo, hemos definido una función anónima que acepta un parámetro. Se ejecuta concurrentemente gracias a go y se le pasa el argumento"Adiós". Es importante notar que para que la ejecución concurrente de funciones anónimas se sincronice adecuadamente, se puede utilizar un WaitGroup, como vimos antes, o simplemente introducir un tiempo de espera temporal (no recomendable para grandes proyectos).
¿Por qué se prefieren los WaitGroups sobre Time.Sleep?
Usar time.Sleep() introduce retrasos explícitos en el código, lo cual no es una práctica eficiente. Si el programa se hace más complejo o exige mayor precisión temporal, esto podría llevar a recursos desperdiciados y problemas de rendimiento. En cambio, los WaitGroup proporcionan un control más preciso y eficiente sobre la sincronización entre distintas partes concurrentes del programa, lo cual es esencial en aplicaciones robustas y de gran escala.
¿Qué esperar de los Channels en Go?
Aunque no se exploraron en profundidad en esta clase, los Channels son otra herramienta poderosa para manejar la comunicación entre Goroutines. A diferencia de los WaitGroup que permiten esperar a que las rutinas se completen, los Channels permiten enviar y recibir datos entre Goroutines. Esto desbloquea la capacidad de crear estructuras de programación más complejas y eficientes que pueden ser más robustas y fáciles de mantener a largo plazo.
Es vital para todo programador de Go explorar las distintas herramientas de concurrencia y encontrar la que mejor se adapte a sus necesidades específicas. ¡La práctica, junto con el aprendizaje continuo, perfeccionará tus habilidades!
Tuve que ver un par de veces la clase para entender, aquí comparto el código con ++comentarios++ con la finalidad de que quede un poco mas claro el tema 😃 espero les sea de ayuda.
Peguen el codigo en VSCode para una mejor legibilidad jeje
package main
import("fmt""sync""time")func say(text string, wg *sync.WaitGroup){// Gorutine defer wg.Done()// Esta linea se va a ejecutar hasta el final de la funcion, y de esta forma libera el gorutine del WaitGroup fmt.Println(text)}func main22(){var wg sync.WaitGroup// El paquete sync permite interacturar de forma primitiva con las gorutine. Variable que acomula un conjunto de gorutines y los va liberando poco a poco fmt.Println("Hello") wg.Add(1)// Indicamos que vamos a agregar 1 Gorutine al WaitGroup para que espere su ejecucion antes de que la gurutine base (main) muera, y así le de tiempo a la siguiente gorutine de ejecutarse go say("world",&wg)// la palabra reservada go ejecutará la funcion de forma concurrente wg.Wait()// Funcion del WaitGroup que sirve para decirle al gorutine principal (main) que espere hasta que todas las gorutine del WaitGroup finalicen, es decir, hasta que se ejecute 'defer wg.Done()' en cada una de las goroutines go func(text string){// Funciona anonima fmt.Println(text)}("Adios") time.Sleep(time.Second*1)// ! Funcion para que cuando llegue a esta linea espere el tiempo indicado (lo suficiente para que la Gorutine ejecute su funcion de forma concurrente)// Nota: Para fines practicos se hace uso de la funcion Sleep(), pero en realidad NO es una buena practica, es mejor utilizar los WaitGroups}
gracias totales
muchas gracias, me ayudo a entender
Me costó un poco de trabajo entender esta clase. Así que aquí va mi aporte:
Un WaitGroup espera a que una colección de goroutines termine su ejecución.
Para esto se una la WaitGroup.Add() ( wg.add(1) en el ejemplo de la clase).
El número entero indica el número de goroutines que debe esperar para finalizar la ejecución de la goroutine principal.
Cada vez que una goroutine termina su ejecución, llama el método Done(). Esto hace que el contador del WaitGroup se reduzca.
Cuando el contador llegue a zero la rutina principal continuará su ejecución.
La función wait() bloquea la rutina principal hasta que todas las demás rutinas del grupo hayan terminado.
Excelente aporte! Gracias
Para cualquiera que llegue a esta clase primero vean este link
https://gobyexample.com/waitgroups
les va a ayudar demasiado a entender los conceptos vistos en el video.
Considero que esta clase puede explicarse de mejor manera, siendo honesto vi el video y no logré entender los conceptos de los que se habla en el video.
Tuve que hacer un poco de research para poder entenderlo por completo.
Para la gente que viene de JavaScript la manera en que logré entender los WaitGroups fue comparándolos con un arreglo de promesas, en JS podemos crear un arreglo de promesas y hacer un await de ese arreglo de promesas para esperar a que se resuelvan.
Viéndolo de ese modo cuando creamos el WaitGroupvar wg sync.WaitGroup
es como crear nuestro arreglo de promesas vacio
y cada que hacemos
wg.Add(1)
y agregamos una go routine
go someFunc()
es como pushear una promesa al array de promesas
y finalmente cuando hacemos wg.Wait() es como hacer un await de nuestro arreglo de promesas.
Yo que vengo de JS, me ayudo bastante verlo de este modo para entender como funcionan los WaitGroup.
Mucho mejor explicado de esta forma
No agregaste un wg.Add(1) para la función anónima. Esto quiere decir que si usamos Gorutines con funciones anónimas no es necesario hacer uso de WaitGroup?
No tiene que ver eso, en vez de usar:
wg.Add(1)
Usa:
time.Sleep(time.Second *1)
Gracias Eliaz me lo aclaraste tenia la misma duda que Cesar!
es algo parecido a las promesas en JS no? el mismo principio? que piensan?
Es parecido, pero en Go está en un hilo de procesamiento distinto al del código de main.go que se está ejecutando.
Hilos (Concurrencia)
💡 La concurrencia es un paradigma que indica la manera de ejecución de algunos procesos.
.
Podemos denotar 2 aspectos importantes en este tema:
Sincronizmo: Ejecución secuencial bloqueable.
Asíncronizmo: Ejecución paralela de procesos
.
En este punto, JavaScript se ve vulnerable porque la concurrencia no es uno de sus fuertes y por ello, pudiera romper el concepto a algunos.
.
Definición
💡 Un hilo, es una línea de ejecución de algún proceso que fundamentalmente no es ni sincrónico, ni asincrónico.
.
Mencionamos que la concurrencia es un paradigma ya que nos permite realizar procesos delegados según sea SO, su peso o prioridad.
.
Les dejo un ejemplo para que evalúen la concurrencia desde otro enfoque.
La explicación no fue muy clara para mi, estoy viendo el video un par de veces pero creo que son mas claros los comentarios de los demás compañeros.
El waitgroup es muy parecido al manejo de promesas en javascript.
Es algo muy muy similar al asincronismo en JavaScript
Parece que los WaitGroup se usan para agrupar funciones que pueden correr en forma concurrente pero sin compartir información. Y son muy primitivos, entonces es mejor conocerlos pero usar Channels en su lugar.
Para ser sincero no entendí nada de eso de los go routines
En mis palabras, concurrencia y paralelismo. Espero que les sea de ayuda.
La concurrencia es el conjunto de mecanismos por los cuales podemos correr varios procesos o hilos de ejecución al mismo tiempo. Por ejemplo, tener varios núcleos en el procesador y que cada uno se encargue de un proceso, o dando un cachito de tiempo a cada proceso de forma intercalada (multiplexación en el tiempo). Incluso, podemos pensar la concurrencia a nivel de un conjunto de máquinas, cada una ejecutando programas diferentes que se comunican entre sí.
El paralelismo, por otro lado, es dividir un problema en varias partes y resolver cada una en simultáneo. Al final, podemos usar cada uno de los resultados para crear una solución al problema original (forkJoin).
Si encuentran alguna mejora, háganme saber.
🤯
Mi ejercicio para practicar
package main
import("fmt""sync")func mostrar(a int, wg *sync.WaitGroup){ fmt.Println(a) wg.Done()}func main(){var slice =[]int{1,2,3,4,5,6,7,8,9,10}var wg sync.WaitGroup wg.Add(len(slice))fori:= range slice { go mostrar(i,&wg)} wg.Wait()}
package main
import("fmt""sync""time")func say(text string, wg *sync.WaitGroup){ defer wg.Done() fmt.Println(text)}func main(){var wg sync.WaitGroup fmt.Println("Hello") wg.Add(2) go say("world",&wg) go say("!",&wg) wg.Wait() go func(text string){ fmt.Println(text)}("Adios") time.Sleep(time.Second*1)}