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.

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.