DMarlon Studio

Lección 11 · 11 de 20

Goroutines y WaitGroup · Go

Ejecute trabajo en paralelo con goroutines en Go, espere a que terminen con sync.WaitGroup y obtenga siempre la misma salida.

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

Una goroutine es una función que se ejecuta a la vez que el resto del programa. Basta con escribir `go` delante de la llamada: go func(i int) { ... }(i) lanza una por documento y el bucle sigue sin esperar. Son muy ligeras; un programa puede tener miles.

El problema es que main no espera a nadie: si termina, el programa acaba con las goroutines a medias. `sync.WaitGroup` lleva la cuenta: wg.Add(1) antes de lanzar cada una, defer wg.Done() dentro, para avisar al acabar pase lo que pase, y wg.Wait() bloquea main hasta que la cuenta vuelve a cero.

Cada goroutine escribe solo en su casilla, counts[i]. Como ninguna toca la de otra, no comparten nada y no hace falta ningún cerrojo. Si todas sumaran en una misma variable, habría una condición de carrera y el total podría salir mal; eso se resuelve en la lección del mutex.

i se pasa como argumento a la función anónima. Antes de Go 1.22, la variable del bucle era una sola que va cambiando, y una goroutine que la leyera directamente podría ver ya el valor de otra vuelta. Con el argumento, cada goroutine recibe su propia copia.

Las goroutines terminan en un orden que cambia de una ejecución a otra, pero la salida no: se escribe después de wg.Wait() y recorriendo counts en orden. Esa es la regla para resultados deterministas: calcular en paralelo y escribir al final, en un orden fijo.

Código · main.go

package mainimport (	"fmt"	"strings"	"sync")func main() {	names := []string{"intro.txt", "setup.txt", "faq.txt"}	texts := []string{		"Go runs functions concurrently with goroutines",		"install the toolchain, then build and run the program",		"why share memory by communicating instead of communicating by sharing memory",	}	counts := make([]int, len(texts)) // one slot per goroutine: nothing is shared	var wg sync.WaitGroup	for i := range texts {		wg.Add(1)        // one more goroutine to wait for		go func(i int) { // i is passed as an argument: each goroutine has its own copy			defer wg.Done()			counts[i] = len(strings.Fields(texts[i]))		}(i)	}	wg.Wait() // blocks until every goroutine has called Done	total := 0	for i, n := range counts { // printed in order, whatever order they finished in		fmt.Printf("%-10s %2d words\n", names[i], n)		total += n	}	fmt.Println("total:", total, "words in", len(texts), "documents")}

Salida · go run main.go

intro.txt   6 words
setup.txt   9 words
faq.txt    11 words
total: 26 words in 3 documents

Para recordar

  • go f() ejecuta f en una goroutine nueva.
  • wg.Add(1) antes, defer wg.Done() dentro, wg.Wait() para esperar.
  • Si cada goroutine escribe en su casilla, no comparten nada.
  • Pase la variable del bucle como argumento a la goroutine.
  • Calcule en paralelo y escriba al final, en orden.

Ejercicio

Haga que cada goroutine calcule también la palabra más larga de su texto, guárdela en un segundo slice longest y escríbala junto al número de palabras.