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.
Leave Channels en Go (Golang) to:
Read more #cervantes posts
Best Posts From Orlando Monteverde
We have not curated any of orlmicron's posts yet. But you can encourage our curation team to review posts by visiting them regularly and by referring other readers. Because we give priority to frequently read content.
More Posts From Orlando Monteverde
- Channels en Go (Golang)
- Goroutines y el WaitGroup en Go (Golang)
- Conociendo las gorutinas en Go (Golang)
- Conociendo Panic y Recover en Go (Golang)
- Manejo de errores en Go (Golang)
- Interfaces vacías en Go (Golang)
- Interfaces en Go (Golang)
- Estructuras: ¿Las Clases de Go (Golang)?
- Tipos de datos Personalizados y Métodos en Go (Golang)
- Paquetes de terceros y alias en Go (Golang)