Channels en Go (Golang)
Ahora que conocemos las goroutines y el WaitGroup es momento de hablar de los channels, un tipo de dato muy ligado a las Goroutines y a la concurrecia en Go
Channels
Un channel es un tipo de variable que permite el paso de información entre gorourines. Me parece la manera más sencilla de definirlo. Aunque, es posible que no quedara del todo claro, así que veamos a los channels en acción.
package main
import (
"fmt"
"math/rand"
"time"
)
func imprimir(ch chan string, texto string) {
for {
// Pasando información al channel.
ch <- texto
// Tiempo de espera entre cada iteración.
// rand.Int nos permite generar un entero de manera aleatoria
// time.Sleep requiere que la variable sea de tipo time.Duration
time.Sleep(time.Duration(rand.Int()) * time.Nanosecond)
}
}
func main() {
// Creando un channel de tipo string.
ch := make(chan string)
// Pasando el channel a dos goroutines.
go imprimir(ch, "primera")
go imprimir(ch, "segunda")
// Ciclo infinito que imprime la información que viene en el channel.
for {
fmt.Println(<-ch)
}
}
Comencemos por la declaración del channel ch := make(chan string)
, hacemos uso de la función make
como hemos visto antes con los maps y los slices. Pero no declaramos un channel simplemente, especificamos además el tipo de variable que va a manejar, en este caso es de tipo string y este será el único tipo de dato que aceptará el channel. Sin embargo, es posible crear channels de cualquiera de los tipos vistos anteriormente, incluyendo las estructuras.
La llamada a la función imprimir
no aporta nada realmente nuevo, usamos la palabra reservada go
para crear una nueva goroutine y le pasamos el channel recientemente creado y una cadena de texto. Finalmente mandamos a imprimir el contenido del channel dentro de un ciclo infinito.
En este punto si podemos notar algo interesante, ese operador semejante a una flecha <-ch
. De esta forma extraemos datos del channel. Si ahora observamos la función imprimir
, encontramos un caso semejante: ch <- texto
. Esta vez no extraemos información del channel sino que la introducimos al mismo.
Channels de una dirección
Un detalle interesante sobre los channels es que podemos establecer que sólo trabajen en un sentido, es decir, que exclusivamente reciban datos o los envíen. Modificando un poco la función imprimir
, podemos ver la sintaxis requerida para este fin.
...
func imprimir(ch chan<- string, texto string) {
for {
ch <- texto
time.Sleep(time.Duration(rand.Int()) * time.Nanosecond)
}
}
...
Apenas se puede notar la diferencia, así que les diré donde deben mirar: chan<-
. La flecha apuntando al la palabra reservada chan
indica que este channel sólo puede recibir información. Esto hace fácil deducir la sintaxis utilizada para establecer un channel que envía información: <-chan
.
Cerrando un channel
Podemos cerrar un channel haciendo uso de la función close
, pasando como argumento el channel a cerrar. Por ejemplo:
...
func imprimir(ch chan<- string, texto string) {
for i := 0; i < 5; i++ {
ch <- texto
time.Sleep(time.Duration(rand.Int()) * time.Nanosecond)
}
close(ch)
}
...
Podemos ver que esta vez el ciclo es finito, por lo que se imprimirá 5 veces cada palabra antes de cerrar el ch por medio de la función close
. Sin embargo, al ejecutarlo notarán que posterior a las 5 impresiones comenzarán a imprimir cadenas vacías. Finalmente, obtenemos un mensaje como esta: panic: close of closed channel
.
La razón es que aunque ya está cerrado el channel, seguímos solicitando información, y este nos envía el valor cero del tipo de dato, en este caso, una cadena vacía. Respecto al panic
, ya que estamos ejecutando la función imprimir
en dos goroutines diferentes, la primera en concluir cerrará el channel, la otra intentará hacer los mismo, pero debido a que el channel ya se encuentra cerrado se produce el panic
.
Confirmando si un channel se encuentra cerrado
Si queremos evitar que sucedan casos como el anterior, sería conveniente confirmar si un channel se encuentra cerrado o no antes de trabajar con él. Para este fin, podemos recurrir al segundo valor obtenido al extraer valores de un channel, un boolean
que indica si el channel se encuentra abierto. Su sintaxis es la siguiente: valor, booleano := <- channel
. Modifiquemos el código anterior.
...
func main() {
ch := make(chan string)
go imprimir(ch, "primera")
go imprimir(ch, "segunda")
for {
valor, ok := <-ch
if !ok {
break
}
fmt.Println(valor)
}
}
Ahora confirmamos si el channel se encuentra cerrado y terminamos el ciclo. Con esto la ejecución del programa finaliza una vez que el channel se encuentra cerrado. Otra manera de hacer esto, y una que me gusta más, es usar el for-range.
...
for valor := range ch {
fmt.Println(valor)
}
...
Publicaciones relacionadas
Gracias por leer, espero que este artículo te resultara de provecho. Si así fue, no dudes en dejar un comentario, compartirlo y votar. Te invito a comentar cualquier duda o sugerencia, te aseguro que las leo todas. Así que, por favor, ayúdame a mejorar y continuar compartiendo contenido de calidad. Si te gusta la programación y/o la informática en general, te invito a formar parte de la comunidad Develop Spanish dónde compartimos contenido de esa naturaleza y totalmente en español. Hasta la próxima.
Hello! Your post has been resteemed and upvoted by @ilovecoding because we love coding! Keep up good work! Consider upvoting this comment to support the @ilovecoding and increase your future rewards! ^_^ Steem On!
Reply !stop to disable the comment. Thanks!
Congratulations @orlmicron! You have completed the following achievement on the Steem blockchain and have been rewarded with new badge(s) :
Click here to view your Board of Honor
If you no longer want to receive notifications, reply to this comment with the word
STOP
Do not miss the last post from @steemitboard:
Congratulations @orlmicron! You received a personal award!
You can view your badges on your Steem Board and compare to others on the Steem Ranking
Do not miss the last post from @steemitboard:
Vote for @Steemitboard as a witness to get one more award and increased upvotes!