Skip to content

Latest commit

 

History

History
242 lines (169 loc) · 5.87 KB

goroutines.md

File metadata and controls

242 lines (169 loc) · 5.87 KB

1. Goroutines en Go

1.1 Introducción

Go es un lenguaje especializado en la concurrencia . Es un lenguaje que fue diseñado para manejar múltiples tareas de manera asíncrona. Esta entrada trata sobre los channels o canales de go.

Antes de empezar, recuerda que paralelismo y concurrencia son diferentes.

Podríamos decir que un programa es concurrente si puede soportar dos o más acciones en progreso.

Por otro lado, un programa es paralelo si puede soportar dos o más acciones ejecutándose simultáneamente.

1.2 Corrutinas en Go

Una corrutina , en go, es una función o método que se ejecuta concurrentemente junto con otras funciones o métodos. En go, a las corrutinas se les conoce como goroutines o gorutinas. Incluso, la función principal, main, se ejecuta dentro de una.

Para generar una goroutine agregamos el keyword go antes de una función. Lo anterior programará la función para su ejecución asíncrona.

package main

func write(texto string) {
  fmt.Println(texto)
}

func main() {
  fmt.Println("hey")
  go write("hey again")
}

En el caso anterior, debido a su naturaleza asíncrona, la goroutine no detiene la ejecución del código. Lo anterior implica que el cuerpo de la función main continua su ejecución y nuestra goroutine nunca llega a ejecutarse.

flowchart LR
    main --> id0(print hey)
    id0 --> id1(fin del programa)
    id0 --> id3(go write)
    id1 --> id2(aqui termina el programa)
Loading

Siendo el output:

hey

El programa termina antes de que la goroutine se ejecute.

¿Pero entonces? ¿cómo hacemos para que nuestra goroutine se ejecute? La aproximación ingenua sería usar un sleep para pausar la ejecución del código. Esto, como ya sabes, es un sinsentido. ¡No use un sleep!

// NO LO HAGAS
time.Sleep(1 * time.Second)

Una mejor aproximación sería crear un WaitGroup o grupo de espera.

1.3 WaitGroups en Go

Un WaitGroup detendrá la ejecución del programa y esperará a que se ejecuten las goroutines.

Internamente, un WaitGroup funciona con un contador, cuando el contador esté en cero la ejecución del código continuará, mientras que si el contador es mayor a cero, esperará a que se terminen de ejecutar las demás goroutines.

package main

import (
 "fmt"
 "sync"
)

func main() {
 var wg sync.WaitGroup

 wg.Wait()
 fmt.Println("Si el contador del waitgroup es mayor que cero se continuará con esta función.")
}

siendo su output

Si el contador del waitgroup es mayor que cero se continuará con esta función.

En el ejemplo en vivo

¿Pero como deberíamos cambiar el valor del contador?

Go dispone para nosotros de dos métodos para este propósito, para incrementar y decrementar el contador del WaitGroup usaremos los métodos Add y Done, respectivamente.

1.3.1 El método Add

El método Add incrementa el contador del WaitGroup en n unidades, donde n es el argumento que le pasamos.

El truco está en llamarlo cada vez que ejecutemos una goroutine.

package main

import (
  "fmt"
+ "sync"
)

func write(texto string) {
  fmt.Println(texto)
}

func main() {
+ var wg sync.WaitGroup

  fmt.Println("hey")

+ wg.Add(1)
  go write("hey again")

+ wg.Wait()
}

1.3.2 El Método Done

El método Done se encarga de disminuir una unidad del contador del WaitGroup. Lo llamaremos para avisarle al WaitGroup que la goroutine ha finalizado y decremente el contador en uno.

-func write(texto string) {
+func write(texto string, wg *sync.WaitGroup) {
  fmt.Println(texto)
+ wg.Done()
}

Important

Recuerda que la instancia del WaitGroup (wg *) necesita pasarse por referencia o de otra manera no accederemos al WaitGroup original.

-func write(texto string) {
+func write(texto string, wg *sync.WaitGroup) {
  fmt.Println(texto)
+ defer wg.Done()
}

Tip

Usa defer sobre el método Done para garantizar que sea lo último que se ejecute.

flowchart LR
    main --> id0(print hey)
    id0 --> id1(fin del programa)
    id0 --> id3(go write)
Loading

Siendo el código definitivo

package main

import (
  "fmt"
  "sync"
)

func write(texto string, wg *sync.WaitGroup) {
  fmt.Println(texto)
  defer wg.Done()
}

func main() {
  var wg sync.WaitGroup

  fmt.Println("hey")

  wg.Add(1)
  go write("hey again", &wg)

  wg.Wait()
}

Siendo el output

hey
hey again

En el ejemplo en vivo

En este ejemplo si que Wait se espera a que se terminen de ejecutar todas las goroutines.

flowchart LR
    main --> id0(print hey)
    id3(wg.Add :: go write :: wg.Done) --> id4(wg.Wait)
    id4 --> id1(fin del programa)
    id0 --> id3
Loading

Una vez que el contador de wg.Wait se vuelve cero, se continua la ejecución del programa.

var wg sync.WaitGroup
wg.Add(1)
go escribirEnCanal("Ge", &wg)
wg.Wait()

1.4 Funciones anónimas en goroutines

Cuando se usan gorutinas, es bastante común utilizar funciones anónimas para evitar declarar una función nueva.

go func() {
}()

Recuerda que los paréntesis que aparecen tras el cuerpo de la función ejecutan la función anónima que declaramos y también reciben sus argumentos.

go func(text string) {
}("Texto")

2. References

Introduccion a las goroutines y concurrencia

Goroutines en golanbot

Goroutines por google devs