DMarlon Studio

Lección 14 · 14 de 20

Grupos de trabajadores · Go

Reparta trabajo entre un número fijo de goroutines en Go con un canal de tareas y otro de resultados, y ordene los resultados antes de escribirlos.

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

Un grupo de trabajadores (*worker pool*) limita cuántas tareas se hacen a la vez: en lugar de una goroutine por factura, hay tres que se reparten todas. Es lo que se hace cuando cada tarea gasta un recurso escaso, como conexiones a una base de datos o llamadas a una API.

Hay dos canales con búfer: jobs, con los números de factura pendientes, y results, con una línea de texto por factura. worker los recibe con dirección: <-chan int solo para recibir y chan<- string solo para enviar, así el compilador impide usarlos al revés.

Cada trabajador ejecuta for id := range jobs: toma una factura, le suma el 21 % de IVA, envía el resultado y vuelve a por otra. Los tres compiten por las mismas tareas, y cuál hace cada una cambia en cada ejecución. close(jobs) avisa de que no hay más trabajo, y sus bucles terminan.

main sabe que habrá exactamente un resultado por factura, así que for range nets recibe len(nets) veces y no necesita un WaitGroup. Leer el mapa nets desde varias goroutines a la vez es seguro porque nadie lo modifica.

Las facturas se envían recorriendo un mapa, cuyo orden es aleatorio, y los trabajadores terminan cuando terminan: los resultados llegan desordenados. `sort.Strings(lines)` los pone en orden (todas las líneas empiezan por # y un número de tres cifras) y la salida es siempre la misma.

Código · main.go

package mainimport (	"fmt"	"sort")func worker(nets map[int]float64, jobs <-chan int, results chan<- string) {	for id := range jobs { // takes invoice IDs until jobs is closed		results <- fmt.Sprintf("#%d %7.2f -> %7.2f", id, nets[id], nets[id]*1.21)	}}func main() {	nets := map[int]float64{101: 120, 102: 80, 103: 45.5, 104: 300, 105: 19.99, 106: 64}	jobs := make(chan int, len(nets))	results := make(chan string, len(nets))	for w := 1; w <= 3; w++ { // a pool of three workers shares the jobs		go worker(nets, jobs, results)	}	for id := range nets { // map order is random, and so is the order of results		jobs <- id	}	close(jobs) // no more work: the workers' loops end	var lines []string	for range nets { // exactly one result per job		lines = append(lines, <-results)	}	sort.Strings(lines) // sort before printing: the same output every run	for _, line := range lines {		fmt.Println(line)	}	fmt.Println(len(lines), "invoices, VAT added by 3 workers")}

Salida · go run main.go

#101  120.00 ->  145.20
#102   80.00 ->   96.80
#103   45.50 ->   55.05
#104  300.00 ->  363.00
#105   19.99 ->   24.19
#106   64.00 ->   77.44
6 invoices, VAT added by 3 workers

Para recordar

  • Un grupo fijo de trabajadores limita el trabajo simultáneo.
  • Los trabajadores leen de un canal de tareas con range.
  • close(jobs) termina los bucles de los trabajadores.
  • Si sabe cuántos resultados habrá, reciba exactamente esos.
  • Los resultados llegan en cualquier orden: ordénelos antes de escribir.

Ejercicio

Cambie el número de trabajadores a 1 y después a 6, y compruebe que la salida no cambia. Luego haga que worker reciba también un número de trabajador y añádalo al final de cada línea: verá que entonces la salida deja de ser siempre igual.