40 - Goroutines - Programación concurrente

Todos los programas que hemos aprendido a desarrollar se ejecutan en forma secuencial, por ejemplo cuando llamamos a una función desde la main hasta que esta no finalice no continúan ejecutándose las instrucciones de la main.

Esta forma de resolver los problemas en muchas situaciones no será la más eficiente.
Imaginemos si implementamos una función que busca en el disco duro la cantidad de archivos con extensión go, este proceso requiere de mucho tiempo ya que el acceso a disco es costoso en tiempo.

Mientras esta búsqueda no finalice nuestro programa no puede desarrollar otras actividades, por ejemplo no podría estar accediendo a un servidor de internet, procesando tablas de una base de datos etc.

En el lenguaje Go se ha creado el concepto de Goroutine para poder ejecutar funciones en forma concurrente, es decir que comience la ejecución de la función pero continúe con la ejecución de la función main o la función desde donde se llamó la Goroutine.

Con las Goroutines podremos sacar ventajas en seguir la ejecución del programa y que no se bloquee en algoritmos complejos o que accedan a recursos lentos.

Otra gran ventaja con el empleo de las Goroutienes es que los computadores actuales tienen múltiples procesadores y podremos ejecutar en esos casos Goroutines en forma paralela, con esto nuestros programas se ejecutarán mucho más rápido.

Problema 1:

Mostrar el número "0" mil veces y el número "1" mil veces. Utilizar la programación secuencial vista hasta ahora.

Programa: ejercicio163.go

package main

import "fmt"

func mostrar0() {
    for f := 1; f <= 1000; f++ {
        fmt.Print("0-")
    }
}

func mostrar1() {
    for f := 1; f <= 1000; f++ {
        fmt.Print("1-")
    }
}

func main() {
    mostrar0()
    mostrar1()
}

Este programa como vemos desde la función main llama a la función "mostrar0", recién cuando finaliza la ejecución por completo de la función "mostrar0" la función main pasa a llamar a la función "mostrar1". Cuando finaliza la ejecución de "mostrar1" continúa la función main donde se encuentra la llave de cerrado y finaliza la ejecución del programa.

Como podemos comprobar al ejecutar el programa se muestran 1000 números "0" seguidos y luego 1000 números "1" seguidos:

programación secuencial

Para ejecutar una función en forma concurrente veremos que solo debemos anteceder la palabra clave go cuando llamamos a una función.

Problema 2:

Mostrar el número "0" mil veces y el número "1" mil veces. Utilizar la programación concurrente.

Programa: ejercicio164.go

package main

import "fmt"

func mostrar0() {
    for f := 1; f <= 1000; f++ {
        fmt.Print("0-")
    }
}

func mostrar1() {
    for f := 1; f <= 1000; f++ {
        fmt.Print("1-")
    }
}

func main() {
    go mostrar0()
    go mostrar1()
    var continua string
    fmt.Scan(&continua)
}

Si ejecutamos este programa veremos una salida por pantalla parecida a esta (como vemos los ceros y unos se mezclan y no aparecen 1000 ceros y luego 1000 unos):

programación concurrente

No tenemos asegurado en que orden y cuanto tiempo se le asigna a cada función para que se ejecute, pero queda claro que se va repartiendo el tiempo de ejecución y acceso a la pantalla para mostrar los ceros y unos.

Cuando comienza a ejecutarse el programa desde la main se llama la goroutine mostrar0:

func main() {
    go mostrar0()

Pero la main no se bloquea hasta que finalice la función "mostrar0" sino que continúa con la ejecución se las siguientes instrucciones, es decir llama a la función "mostrar1":

func main() {
    go mostrar1()

Nuevamente no se bloquea y por eso hemos dispuesto una forma de bloquear la función main hasta que el operador cargue una cadena por teclado:

    var continua string
    fmt.Scan(&continua)
}

Si la función main finaliza todas las goroutines que están en ejecución se abortan, por eso la necesidad de detener pidiendo un string por teclado (en el concepto siguiente veremos como hacer más elegante la espera a que finalicen todas las goroutines)