DMarlon Studio

Lección 12 · 12 de 20

Canales · Go

Comunique goroutines en Go con canales: enviar y recibir, cerrar un canal, recorrerlo con range y montar una tubería de dos etapas.

Go · Canales en DMarlon Studio: el código del ejemplo y su salida en la terminal

Un canal es un tubo con tipo por el que las goroutines se pasan valores. make(chan Line) crea uno de líneas de pedido; out <- l envía y <-total recibe. Es la forma que propone Go de coordinar: «no comunique compartiendo memoria; comparta memoria comunicando».

Este canal no tiene búfer: cada envío espera a que alguien reciba, y cada recepción espera a que alguien envíe. Así el canal, además de pasar datos, sincroniza: produce no puede adelantarse a quien lee.

produce es un patrón habitual, el generador: crea el canal, lanza una goroutine que lo llena y lo devuelve enseguida. El tipo de retorno, <-chan Line, es un canal solo de recepción: quien lo recibe no puede enviar ni cerrar. Al terminar, defer close(out) lo cierra para avisar de que no vendrán más valores.

La segunda etapa recorre el canal con for l := range produce(lines), que recibe hasta que el canal se cierra; sin el close, el bucle esperaría para siempre. Escribe cada línea, acumula el importe y envía el total por otro canal. En main, <-total se queda esperando hasta que llega ese valor, sin necesidad de un WaitGroup.

Con un solo emisor y un solo receptor, el orden de los valores se conserva y la salida es siempre la misma. Dos reglas: cierra el canal quien envía, nunca quien recibe, y enviar a un canal cerrado provoca un panic.

Código · main.go

package mainimport "fmt"type Line struct {	Item  string	Qty   int	Price float64}func produce(lines []Line) <-chan Line { // sends every line, then closes the channel	out := make(chan Line) // unbuffered: each send waits for a receiver	go func() {		defer close(out)		for _, l := range lines {			out <- l		}	}()	return out}func main() {	lines := []Line{{"Coffee", 2, 3.5}, {"Croissant", 3, 1.8}, {"Orange juice", 1, 2.9}}	total := make(chan float64)	go func() { // a second stage: receives the lines, sends back one total		sum := 0.0		for l := range produce(lines) { // the loop ends when produce closes its channel			fmt.Printf("%-13s %d x %.2f\n", l.Item, l.Qty, l.Price)			sum += float64(l.Qty) * l.Price		}		total <- sum	}()	fmt.Printf("total: %.2f EUR\n", <-total) // main waits here until the total arrives}

Salida · go run main.go

Coffee        2 x 3.50
Croissant     3 x 1.80
Orange juice  1 x 2.90
total: 15.30 EUR

Para recordar

  • ch <- v envía; v := <-ch recibe.
  • Sin búfer, cada envío espera a su receptor: el canal sincroniza.
  • <-chan T es un canal solo de recepción.
  • close(ch) avisa de que no hay más valores; range termina con él.
  • Cierra el que envía; enviar a un canal cerrado es un panic.

Ejercicio

Añada una tercera etapa entre las dos: una función discount(in <-chan Line) <-chan Line que rebaje un 10 % el precio de las líneas con Qty de 3 o más, y encadénela con range discount(produce(lines)).