No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

Range, Close y Select en channels

29/36
Recursos

¿Cómo trabajar efectivamente con los canales en Go?

Manejar canales en Go es una habilidad esencial para quienes buscan aprovechar al máximo la concurrencia. Los canales permiten la comunicación segura entre gorutinas, facilitando así la coordinación de tareas en paralelo. Pero ¿cómo interactuamos con estos canales y sus múltiples valores? Aquí proporcionaremos un recorrido por los conceptos y técnicas necesarios para dominar el uso de canales en Go.

¿Cómo inicializamos y operamos con los canales?

Primero, para crear un canal, utilizamos la función make y especificamos el tipo de dato que manejará. Por ejemplo, para manejar strings:

c := make(chan string)

La novedad es que, esta vez, podemos insertar múltiples mensajes simultáneamente en el canal. Para ello, simplemente enviamos los mensajes al canal:

c <- "mensaje uno"
c <- "mensaje dos"

Al interactuar con canales, dos funcionalidades clave son len y cap. Len indica cuántos datos hay actualmente en el canal, mientras que cap muestra la cantidad máxima de datos que puede almacenar este canal.

¿Cómo cerrar y recorrer los datos de un canal?

Cerrar un canal es crucial una vez que ya no planeamos enviar más datos. Esto se logra con close, indicando al runtime que no se recibirá más información en ese canal. Este paso no solo es una buena práctica sino que optimiza la eficiencia del código:

close(c)

Para recorrer los datos en un canal, utilizamos range dentro de un bucle for. Esta técnica es útil para iterar sobre los datos emitiendo mensajes almacenados:

for msg := range c {
    fmt.Println(msg)
}

Es recomendable cerrar el canal antes de utilizar range para asegurarnos de que no siga esperando más datos.

¿Cómo manejamos múltiples canales?

Cuando se manipulan varios canales y no sabemos cuál responderá primero, el uso de select es indispensable. Para mostrar esto, consideremos dos canales que reciben emails:

email1 := make(chan string)
email2 := make(chan string)

Creamos una función que envía mensajes a estos canales:

func mensaje(msg string, c chan string) {
    c <- msg
}

Al emplear select dentro de un bucle for, podemos manejar la concurrencia de múltiples canales simultáneamente:

for i := 0; i < 2; i++ {
    select {
    case m1 := <-email1:
        fmt.Println("Email recibido de email1:", m1)
    case m2 := <-email2:
        fmt.Println("Email recibido de email2:", m2)
    }
}

Consejo adicional

Cuando trabajes con múltiples canales, ten en cuenta la cantidad y el tipo de datos que cada uno manejará. Esto te ayudará a definir correctamente el ciclo for y garantizar que tu aplicación Go funcione de manera efectiva.

Recuerda, dominar estos conceptos te permitirá manejar concurrencia en Go de manera eficiente. No dudes en plantearte un proyecto donde puedas aplicar estos conocimientos, como un sistema de atención en un hospital, donde la concurrencia es fundamental para optimizar el flujo de trabajo. Estamos seguros de que con práctica y dedicación, podrás lograrlo.

Aportes 28

Preguntas 14

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

  • Estoy trabajando en un proyecto donde se reciben datos del estado de una máquina vending(cantidad de productos, temperatura, etc), se debe guardar y analizar los datos para detectar posibles fallas. Go sería muy útil en el procesamiento de muchas máquinas.
package main

import "fmt"

func message(text string, c chan string) {
	c <- text
}

func main() {
	c := make(chan string, 2)

	c <- "Mensaje 1"
	c <- "Mensaje 2"

	fmt.Println(len(c), cap(c))

	// Range y close
	close(c)
	//c<-"Mensaje 3"

	for message := range c {
		fmt.Println(message)
	}

	// Select
	email1 := make(chan string)
	email2 := make(chan string)

	go message("mensaje 1", email1)
	go message("mensaje 2", email2)

	for i := 0; i < 2; i++ {
		select {
		case m1 := <-email1:
			fmt.Println("Email recibido de email 1", m1)
		case m2 := <-email2:
			fmt.Println("Email recibido de email 2", m2)
		}
	}
}

Hola, vengo a compartir como yo hice el close de mi channel; basicamente comparo si el tamaño de mi channel es mayor o igual la capacidad de este, de ser así se cierra. Lo comparto por que creo que así sería en un proyecto de la vida real, pero no se, si tu ya has trabajado con channels en tu trabajo, de ser así por favor comentame si estoy siendo acertado con mi código o no.

if len(c) >= cap(c) {
	close(c)
	fmt.Println("channel closed")
}

A continuación dejo un ejemplo extraído de https://tour.golang.org/concurrency/5 al cual le he añadido comentarios explicando lo que sucede. Este ejemplo me ha ayudado a entender mejor como funciona el “go” y “select”.

package main

import "fmt"

func fibonacci(c, quit chan int) {
	x, y := 0, 1
	for {
		select {
		case c <- x:
			x, y = y, x+y
		case <-quit:
			fmt.Println("quit")
			return
		}
	}
}

func main() {
	c := make(chan int)
	quit := make(chan int)

	// go func()... se lanza de manera concurrente:
	// la línea fmt.Println(<-c) queda en espera hasta que haya algo que leer de <-c.
	// Al ser lanzado concurrentemente, mientras dicha línea espera, se va a la ejecución de fibonacci(c, quit),
	// que guardará valores en c <- x tantas veces se produzca la espera de la línea fmt.Println(<-c).
	// Una vez sale del bucle, pasa a esperar case <-quit, que será satisfecho con quit <- 0, por lo que imprimirá "quit" y finalizará
	// el programa.
	go func() {
		for i := 0; i < 10; i++ {
			fmt.Println(<-c)
		}
		quit <- 0
	}()
	fibonacci(c, quit)
}

Les comparto este ejercicio de concurrencia tomado del Tour de Go. Consiste en comparar dos árboles para saber si contienen los mismos elementos. En este ejercicio, hago los recorridos en forma concurrente utilizando channels.

La idea es recibir los nodos de los arboles en canales y luego ordenar los nodos para compararlos uno a uno.

package main

import (
	"container/list"
	"fmt"
	"golang.org/x/tour/tree"
	"sort"
)

// Walk walks the tree t sending all values
// from the tree to the channel ch.
func Walk(t *tree.Tree, ch chan int) {
	fmt.Println("Walking", t)
	queue := list.New()
	queue.PushBack(t)
	for queue.Len() > 0 {
		current := queue.Back()
		queue.Remove(current)
		var currentTree = current.Value.(*tree.Tree)

		ch <- currentTree.Value

		if currentTree.Left != nil {
			queue.PushBack(currentTree.Left)
		}

		if currentTree.Right != nil {
			queue.PushBack(currentTree.Right)
		}
	}
	fmt.Println("Finished Walking", t)
}

// Same determines whether the trees
// t1 and t2 contain the same values.
func Same(t1, t2 *tree.Tree) bool {
	chan1 := make(chan int, 10)
	chan2 := make(chan int, 10)
	defer close(chan1)
	defer close(chan2)

	go Walk(t1, chan1)
	go Walk(t2, chan2)

	arr1 := make([]int, 10)
	arr2 := make([]int, 10)

	for i := 0; i < 10; i++ {
		arr1[i] = <-chan1
	}

	for i := 0; i < 10; i++ {
		arr2[i] = <-chan2
	}

	sort.Ints(arr1)
	sort.Ints(arr2)

	for i := 0; i < 10; i++ {
		if arr1[i] != arr2[i] {
			return false
		}
	}

	return true
}

func main() {
	tree10 := tree.New(1)
	anotherTree10 := tree.New(1)
	tree20 := tree.New(2)

	fmt.Println(tree10, " == ", anotherTree10, " true ==", Same(tree10, anotherTree10))
	fmt.Println(tree10, " == ", tree20, "false ==", Same(tree10, tree20))
}

El codigo del programa con algunos apuntes:

package main

import “fmt”

//del ejemplo message SELECT
func message(text string, c chan string) {
c <- text
}

func main() {
//Maneja 2 datos a la vez
c := make(chan string, 2)
// Inserta 2 mensajes
c <- "Mensaje 1"
c <- “Mensaje 2”
// len: cuantos gorutines hay en un channel
// cap: cantidad maxima que puede almacenar ese channel
fmt.Println(len©, cap©)

// Range y close keywork
//CLOSE: le dice al runtime de GO que va a cerrar el canal una vez dejas de usarlo y no recibe ningun otro dato adicional
close(c)

//c<-"Mensaje 3"
//RANGE: Recorrer la lista, reccoriendo todos los mensajes que estan en ese chanel c
for message := range c {
	fmt.Println(message)
}

// Select: multiples canales sin saber  cual va primero
email1 := make(chan string)
email2 := make(chan string)

go message("mensaje 1", email1)
go message("mensaje 2", email2)
// tener presente la cantidad de channels a manejar para el ciclo, en este ejemplo 2
for i := 0; i < 2; i++ {
	select {
	case m1 := <-email1:
		fmt.Println("Email recibido de email 1", m1)
	case m2 := <-email2:
		fmt.Println("Email recibido de email 2", m2)
	}
}

}

En un proyecto de mantos acuiferos distribuidos en una red multi conectada, no me interesa tener los datos secuenciales del flujo de presion y gasto que hay, si se puede, cada segundo.
Me gustaria poder usar coroutines para poder tener una medicion precisa y exacta de este dato, sin la necesidad de esperar a que la medicion en alguna otra locacion termine o concluya su ciclo.

En mi trabajo insertamos varias bases de datos y y tenemos que esperar a que se incerte una para seguir con la otra me parece que esto podria ayudar

Reto

No sé si sea verdad, pero se me hace que se utilizan en la compra de artículos en línea. Por ejemplo compras en cierto tienda en linea multinacional🛫🚚, y los artículos se encuentran en distintas partes del planeta🌎 y necesita coordinar🤓 cada uno de los paquetes de diferentes clientes, eso si es una locura🤯😵, bueno puede aplicarse. ¿Ustedes que opinan?

Creé algo rudimentario de Observador - Observable:

package main

import (
	"fmt"
	"time"
)

func emisorA(output chan<- string) {
	for {
		output <- "Emisor: A"
		time.Sleep(time.Second * 1)
	}
}

func emisorB(output chan<- string) {
	for {
		output <- "Emisor: b"
		time.Sleep(time.Second * 3)
	}
}

func main() {
	canalA := make(chan string)
	canalB := make(chan string)

	go emisorA(canalA)
	go emisorB(canalB)

	for {
		select {
		case cA := <-canalA:
			fmt.Println(cA)
		case cB := <-canalB:
			fmt.Println(cB)
		}
	}

}

En el minuto 4 lo que pasa es que el script queda en estado de deadlock porque hay un mensaje en espera y no hay ningún consumidor de mensajes. No da error por canal lleno.

fatal error: all goroutines are asleep - deadlock!

Al principio no entendía como funcionaba el channel y select, investigando un poco mas me doy cuenta de por que son necesarios para la concurrencia

Las goroutines en Go son ideales para implementar lógica de negocio que requiere concurrencia y paralelismo. Puedes utilizarlas para manejar tareas como: 1. **Procesamiento paralelo**: Ejecutar múltiples operaciones al mismo tiempo, como el manejo de solicitudes de un servidor web. 2. **Tareas asíncronas**: Realizar operaciones que no bloqueen el flujo principal de la aplicación, como acceder a bases de datos o servicios externos. 3. **Manejo de eventos**: Responder a eventos en tiempo real, como notificaciones o actualizaciones de datos. Estas características mejoran la eficiencia y el rendimiento de las aplicaciones.
Los canales en Go son ideales para manejar la concurrencia y la comunicación entre goroutines. Un caso práctico en producción podría ser un sistema de procesamiento de órdenes en tiempo real, donde cada orden se envía a través de un canal. Esto permite que múltiples goroutines se encarguen de procesar las órdenes simultáneamente, mejorando la eficiencia. Además, mediante `select`, puedes monitorear varios canales y responder a las órdenes que llegan primero, optimizando así el tiempo de respuesta.
conversación con chat gpt: Dame 3 ejemplos del mundo real donde podemos sacar provecho de la concurrencia de GolangChatGPTChatGPT Claro, aquí tienes tres ejemplos del mundo real donde se puede sacar provecho de la concurrencia en Go (Golang): 1. **Servicios web de alta concurrencia**: Muchas aplicaciones web, como plataformas de comercio electrónico o redes sociales, experimentan picos de tráfico repentinos. Go es excelente para manejar estas situaciones debido a su capacidad para manejar múltiples solicitudes concurrentes de manera eficiente. Con la concurrencia en Go, puedes diseñar servidores web que puedan manejar miles de solicitudes simultáneas sin degradación del rendimiento. 2. **Procesamiento de datos en tiempo real**: En aplicaciones que requieren procesamiento de datos en tiempo real, como sistemas de monitoreo de redes o análisis de datos de sensores, la concurrencia de Go es invaluable. Puedes diseñar rutinas concurrentes para procesar datos entrantes mientras mantienes la capacidad de respuesta del sistema. Go proporciona primitivas de concurrencia como goroutines y canales que facilitan la construcción de sistemas de procesamiento de datos eficientes. 3. **Herramientas de desarrollo de software**: En el desarrollo de software, especialmente en herramientas como compiladores, linters (analizadores estáticos de código), y sistemas de gestión de paquetes, la concurrencia en Go puede acelerar significativamente los procesos. Por ejemplo, un compilador escrito en Go puede aprovechar la concurrencia para paralelizar la generación de código, lo que resulta en tiempos de compilación más rápidos. De manera similar, los linters pueden realizar análisis estáticos de código en paralelo para mejorar la eficiencia.

Gracias por la explicación, al fin entendí el uso de Select

```python // Este ejemplo usa select sin el select primero ejecutaria la tarea importante que dura 4 segundos y l aesperaría aun cuando importante y normal ya estén listas package main import ( "fmt" "time" ) func urgentTask(c chan string) { time.Sleep(4 * time.Second) c <- "Urgent Task" } func importantTask(c chan string) { time.Sleep(2 * time.Second) c <- "Important Task" } func normalTask(c chan string) { time.Sleep(1 * time.Second) c <- "Normal Task" } func main() { urgentChan := make(chan string) importantChan := make(chan string) normalChan := make(chan string) go urgentTask(urgentChan) go importantTask(importantChan) go normalTask(normalChan) for i := 0; i < 3; i++ { select { case task := <-urgentChan: fmt.Println("Executing:", task) case task := <-importantChan: fmt.Println("Executing:", task) case task := <-normalChan: fmt.Println("Executing:", task) } } } ```package main import ( "fmt" "time") func urgentTask(c chan string) { time.Sleep(4 \* time.Second) c <- "Urgent Task"} func importantTask(c chan string) { time.Sleep(2 \* time.Second) c <- "Important Task"} func normalTask(c chan string) { time.Sleep(1 \* time.Second) c <- "Normal Task"} func main() { urgentChan := make(chan string) importantChan := make(chan string) normalChan := make(chan string) go urgentTask(urgentChan) go importantTask(importantChan) go normalTask(normalChan) for i := 0; i < 3; i++ { select { case task := <-urgentChan: fmt.Println("Executing:", task) case task := <-importantChan: fmt.Println("Executing:", task) case task := <-normalChan: fmt.Println("Executing:", task) } }}

Reto : Hice un channel llamado c que recibe tipos de dato paciente que es un struct, lo hice usando stringers y channels

package main

import "fmt"

type paciente struct {
	id                          int8
	nombre, condicion, urgencia string
}

func newPatient(text paciente, c chan paciente) {
	c <- text
}
func (p paciente) String() string {
	return fmt.Sprintf("Paciente: %d\n - Nombre: %s\n - Condicion: %s\n - Urgencia: %s\n", p.id, p.nombre, p.condicion, p.urgencia)
}
func main() {

	c := make(chan paciente, 3)
	var paciente0 paciente
	paciente0.id = 0
	paciente0.condicion = "Diabetes"
	paciente0.nombre = "Garcia Fernandez"
	paciente0.urgencia = "Leve"
	var paciente1 paciente
	paciente1.id = 1
	paciente1.condicion = "Infarto"
	paciente1.nombre = "Fabricio Gutierrez"
	paciente1.urgencia = "Severo"
	var paciente2 paciente
	paciente2.id = 2
	paciente2.condicion = "Cancer"
	paciente2.nombre = "Gutierrez Jazmit"
	paciente2.urgencia = "Severo"

	go newPatient(paciente0, c)
	go newPatient(paciente1, c)
	go newPatient(paciente2, c)

	for i := 0; i < cap(c); i++ {
		p := <-c
		fmt.Println(p)

	}
}

Propuesta de reto: python es muy lento y yo estoy entendiendo la concurrecia como un scheduler que determina si darle tiempo a la ejecucion de una tarea o no. presento el problema de adqusicion de datos del cerebro ejemplo.

al hacer el sensado de los canales obtenidos del cerebro tenemos muchas señales que no necesariamente son de mi interes por eso he de primero hacer un proceso de filtrado para cada canal.
eso se me ocurre usar la concurrencia para el fin del preprocesado de datos sin tener que esperar a que un lote de tamaño menor a los demas sea ejecutado para poder continuar con los demas

Ok les deji un aporte con unos comentarios que podría facilitar la comprensión del manejo del select y como funciona el flujo de channels

package main

import "fmt"

func message(text string, c chan<- string) {
	c <- text
}

func main() {
	c := make(chan string, 3)

	c <- "Message1"
	c <- "Message2"

	fmt.Println(len(c), cap(c))

	close(c) //Function to close channel
	// c <- "Message2" This wont be added because the channel is already closed

	for message := range c {
		fmt.Println(message)
	}

	email1 := make(chan string)
	email2 := make(chan string)
	go message("message1", email1)
	go message("message2", email2)
	go message("message3", email1)

	for i := 0; i < 3; i++ { //If you perform a for you have to take into account how many message are you waiting
		select { //With select you can migth know which channel is ready
		case m1 := <-email1:
			fmt.Println("Email recibido de email1", m1)
		case m2 := <-email2:
			fmt.Println("Email recibido de email2", m2)
		}
	}
}

Hola compañeros para este curso les dejo mis apuntes todos escritos en go (Son piezas de codigo de cada clase) en este repositorio por si desean volver a estudiarlas.

Por último he hecho este proyecto llamado Gopher’s Supermarket donde quise poner a prueba todos los conocimientos aprendidos en el curso, espero también puedan darle una mirada y hacerme un pull request si lo consideran necesario.

package main

import "fmt"

func fibonacci(c, quit chan int) {
	x, y := 0, 1
	for {
		select {
		case c <- x:
			x, y = y, x+y
		case <-quit:
			fmt.Println("Quitting...")
			return
		}
	}
}

func main() {
	c := make(chan int, 10)
	quit := make(chan int, 10)

	fmt.Println(len(c))
	fmt.Println(cap(c))
	go func() {
		for i := 0; i < 10; i++ {
			fmt.Println(<-c)
		}
		quit <- 0
	}()
	fibonacci(c, quit)
	close(c)
	close(quit)
}

Select

The select statement lets a goroutine wait on multiple communication operations.

select blocks until one of its cases can run, then it executes that case. It chooses one at random if multiple are ready.

Un aporte para terminar de entender la concurrencia en go

golang

😎

Una clase muy interesante

Buena recomendacion sobre cerrar el channel

En mi compañía decidimos usar Go para el manejo de grandes lagos de datos entre SAP y el nuevo portal que vamos manejar, creemos que el manejo de datos trayendo, procesándolos y almacenando en SQL y NoSQL será muy eficiente y escaladle.

len() y cap()