En este tutorial, veremos cómo puede usar canales dentro de sus aplicaciones basadas en Go.
Los canales son tuberías que enlazan entre goroutines
dentro de sus aplicaciones basadas en Go que permiten la comunicación y, posteriormente, el paso de valores hacia y desde variables.
Son increíblemente prácticos y pueden ayudarlo a crear aplicaciones altamente concurrentes de alto rendimiento en Go con un mínimo esfuerzo en comparación con otros lenguajes de programación. Esto no fue de ninguna manera una casualidad, al diseñar el idioma, los desarrolladores principales decidieron que querían que la concurrencia dentro de su idioma fuera un ciudadano de primera clase y que fuera tan fácil trabajar con él como fuera posible, sin ir demasiado lejos y sin permitir a los desarrolladores la libertad en la que necesitan trabajar.
La capacidad de crear sistemas concurrentes tan fácilmente es algo que me atrajo al lenguaje en primer lugar, y tengo que decir que ha sido una luz absoluta hasta ahora.
Nota: Le recomendaría echar un vistazo a mi otro tutorial sobre goroutines si desea aprender más sobre goroutines.
Objetivos
Al final de este tutorial,:
- Tener una sólida comprensión de la teoría detrás de los canales
- Ser capaz de crear aplicaciones de Go simultáneas simples que utilicen canales
Requisitos previos
Para completar este tutorial, deberá haber cumplido los siguientes requisitos:
- Necesitará Ir instalado en su máquina.
Tutorial en vídeo
Si lo desea, este tutorial está disponible en formato de vídeo.
La Teoría
La idea de canales no es nada nuevo, ya que, como muchas de las características de concurrencia de Go, estos conceptos se han desarrollado a partir de Procesos secuenciales Comunicantes de Hoare (1978), CSP para abreviar, e incluso de las figuras de comandos guardados de Dijkstra (1975).
Los desarrolladores de Go, sin embargo, han hecho su misión presentar estos conceptos de una manera simple como sea posible para permitir a los programadores crear aplicaciones mejores, más correctas y altamente concurrentes.
Un ejemplo simple
Comencemos por ver cómo podemos construir un ejemplo realmente simple de cómo funciona en Go. Primero crearemos una función que desaparece y calcula un valor aleatorio y lo pasa de vuelta a una variable de canal llamadavalues
:
Vamos a diseccionar lo que pasó aquí. En nuestra función main()
, llamamos avalues := make(chan int)
, esta llamada creó efectivamente nuestro nuevo canal para que posteriormente pudiéramos usarlo dentro de nuestra CalculateValue
gorroutine.
Nota: Usamos
make
al crear instancias de nuestro canalvalues
como, mapas de similitud y segmentos, los canales deben crearse antes de usarlos.
Después de crear el canal, llamamos a defer close(values)
, lo que impidió el cierre de nuestro canal hasta el final de la ejecución de nuestra función main()
. Por lo general, esto se considera una buena práctica para garantizar que ordenamos después de nosotros mismos.
Después de nuestra llamada a defer
, iniciamos nuestra única goroutine:CalculateValue(values)
pasando a nuestro canal recién creado values
como su parámetro. Dentro de nuestra función CalculateValue
, calculamos un único valor aleatorio entre 1-10, lo imprimimos y luego enviamos este valor a nuestro canal values
llamando a values <- value
.
Volviendo a nuestra función main()
, llamamos a value := <-values
, que devuelve un valor de nuestro canal values
.
Nota-Observe cómo cuando ejecutamos este programa, no termina inmediatamente. Esto se debe a que el acto de enviar y recibir desde un canal está bloqueando. Nuestra función
main()
se bloquea hasta que recibe un valor de nuestro canal.
Al ejecutar este código, debería ver que la salida se ve algo como esto:
$ go run main.goGo Channel TutorialCalculated Random Value: {} 77
Resumen:
myChannel := make(chan int)
– crea myChannel, que es un canal de tipoint
channel <- value
– envía un valor a un canal
value := <- channel
– recibe un valor de un canal
Por lo tanto, la creación de instancias y el uso de canales en sus programas Go parece justo hasta ahora, pero ¿qué pasa en escenarios más complejos?
Canales sin búfer
El uso de un channel
tradicional dentro de sus goroutines a veces puede generar problemas con un comportamiento que puede no estar esperando. Con canalesunbuffered
tradicionales, cada vez que un goroutine envía un valor a este canal,ese goroutine se bloqueará posteriormente hasta que el valor se reciba del canal.
Veamos esto en un ejemplo real. Si echamos un vistazo al siguiente código, es muy similar al código que teníamos anteriormente. Sin embargo, hemos ampliado nuestra funciónCalculateValue()
para realizar un fmt.Println
después de enviar su valor calculado al canal.
En nuestra función main()
, hemos agregado una segunda llamada ago CalculateValue(valueChannel)
, por lo que debemos esperar que se envíen 2 valores a este canal en una sucesión muy rápida.
Sin embargo, cuando ejecute esto, debería ver que solo se ejecuta la primera sentencia finalprint de goroutines:
go run main.goGo Channel TutorialCalculated Random Value: {} 1Calculated Random Value: {} 71Only Executes after another goroutine performs a receive on the channel
La razón de esto es que nuestra llamada a c <- value
se ha bloqueado en nuestra segunda línea y, posteriormente, la función main()
concluye su ejecución antes de que nuestro segundo goroutine
tenga la oportunidad de completar su propia ejecución.
Canales en búfer
La forma de evitar este comportamiento de bloqueo es usar algo llamado canal abuffered. Estos canales en búfer son esencialmente colas de un tamaño determinado que se pueden utilizar para la comunicación entre líneas. Para crear un canal con búfer en lugar de un canal sin búfer, suministramos un argumento de capacidad a nuestro comando make
:
bufferedChannel := make(chan int, 3)
Al cambiar esto a un canal en búfer, nuestra operación de envío, c <- value
solo bloquea dentro de nuestras goroutines en caso de que el canal esté lleno.
Modifiquemos nuestro programa existente para usar un canal en búfer y echemos un vistazo a la salida. Observe que he agregado una llamada a time.Sleep()
en la parte inferior de nuestra funciónmain()
para bloquear perezosamente nuestra función main()
lo suficiente como para permitir que nuestras gorroutines completen la ejecución.
Ahora, cuando ejecutamos esto, deberíamos ver que nuestra segunda goroutine continúa su ejecución independientemente del hecho de que una segunda recepción no se haya llamado en nuestra función main()
. Gracias al time.Sleep()
, podemos ver claramente la diferencia entre los canales sin búfer y su naturaleza de bloqueo y nuestros canales con búfer y su naturaleza de no bloqueo (cuando no están llenos).
Go Channel TutorialCalculated Random Value: {} 1Calculated Random Value: {} 77This executes regardless as the send is now non-blockingThis executes regardless as the send is now non-blocking
Conclusión
Así que, en este tutorial bastante largo, nos las arreglamos para aprender sobre los diferentes tipos de canales dentro de Go. Descubrimos las diferencias entre los canales con y sin búfer y cómo podríamos usarlos en nuestro beneficio dentro de nuestros programas go simultáneos.
Si te ha gustado este tutorial, no dudes en hacérmelo saber en la sección de comentarios a continuación. Si tiene alguna sugerencia sobre lo que podría hacer mejor, ¡me encantaría escucharlas en la sección de comentarios a continuación!
Más información
Si disfrutó de este artículo y desea obtener más información sobre cómo trabajar con Concurrencyin Go, le recomiendo que consulte nuestros otros artículos sobre concurrencia:
- Tutorial Go Mutex
- Tutorial Go Goroutines
- Go sync.Tutorial de Grupo de espera