learn goroutines golang example close and go channel goroutine

golang - ¿Cómo recopilar valores de N goroutines ejecutados en un orden específico?



learn go by example (2)

A continuación se muestra una estructura de tipo Stuff. Tiene tres ints. Un Number , su Double y su Power . Supongamos que calcular el doble y la potencia de una lista dada de entradas es un cálculo costoso.

type Stuff struct { Number int Double int Power int } func main() { nums := []int{2, 3, 4} // given numbers stuff := []Stuff{} // struct of stuff with transformed ints double := make(chan int) power := make(chan int) for _, i := range nums { go doubleNumber(i, double) go powerNumber(i, power) } // How do I get the values back in the right order? fmt.Println(stuff) } func doubleNumber(i int, c chan int) { c <- i + i } func powerNumber(i int, c chan int) { c <- i * i }

El resultado de fmt.Println(stuff) debería ser el mismo que si se inicializaran cosas como:

stuff := []Stuff{ {Number: 2, Double: 4, Power: 4} {Number: 3, Double: 6, Power: 9} {Number: 4, Double: 8, Power: 16} }

Sé que puedo usar <- double y <- power para recopilar valores de los canales, pero no sé qué double / powers pertenecen a qué números.


Las goroutines se ejecutan simultáneamente, de forma independiente, por lo que sin sincronización explícita no se puede predecir el orden de ejecución y finalización. Por lo tanto, no puede emparejar los números devueltos con los números de entrada.

Puede devolver más datos (por ejemplo, el número de entrada y la salida, envueltos en una estructura, por ejemplo), o pasar punteros a las funciones del trabajador (lanzadas como nuevas rutinas), por ejemplo *Stuff Rellenar y hacer que las rutinas llenen los datos calculados en el Stuff sí.

Devolviendo más datos

Usaré un canal tipo chan Pair donde Pair es:

type Pair struct{ Number, Result int }

Entonces el cálculo se verá así:

func doubleNumber(i int, c chan Pair) { c <- Pair{i, i + i} } func powerNumber(i int, c chan Pair) { c <- Pair{i, i * i} }

Y usaré un map[int]*Stuff porque los datos recopilables provienen de múltiples canales ( double y de power ), y quiero encontrar el Stuff apropiado de manera fácil y rápida (se requiere un puntero para poder modificarlo "en el mapa" )

Entonces la función principal:

nums := []int{2, 3, 4} // given numbers stuffs := map[int]*Stuff{} double := make(chan Pair) power := make(chan Pair) for _, i := range nums { go doubleNumber(i, double) go powerNumber(i, power) } // How do I get the values back in the right order? for i := 0; i < len(nums)*2; i++ { getStuff := func(number int) *Stuff { s := stuffs[number] if s == nil { s = &Stuff{Number: number} stuffs[number] = s } return s } select { case p := <-double: getStuff(p.Number).Double = p.Result case p := <-power: getStuff(p.Number).Power = p.Result } } for _, v := range nums { fmt.Printf("%+v/n", stuffs[v]) }

Salida (pruébalo en Go Playground ):

&{Number:2 Double:4 Power:4} &{Number:3 Double:6 Power:9} &{Number:4 Double:8 Power:16}

Usando punteros

Como ahora estamos pasando los valores de *Stuff , podemos "rellenar previamente" el número de entrada en el Stuff mismo.

Pero se debe tener cuidado, solo puede leer / escribir valores con la sincronización adecuada. Lo más fácil es esperar a que todas las gorutinas "trabajadoras" terminen sus trabajos.

var wg = &sync.WaitGroup{} func main() { nums := []int{2, 3, 4} // given numbers stuffs := make([]Stuff, len(nums)) for i, n := range nums { stuffs[i].Number = n wg.Add(2) go doubleNumber(&stuffs[i]) go powerNumber(&stuffs[i]) } wg.Wait() fmt.Printf("%+v", stuffs) } func doubleNumber(s *Stuff) { defer wg.Done() s.Double = s.Number + s.Number } func powerNumber(s *Stuff) { defer wg.Done() s.Power = s.Number * s.Number }

Salida (pruébalo en Go Playground ):

[{Number:2 Double:4 Power:4} {Number:3 Double:6 Power:9} {Number:4 Double:8 Power:16}]


Personalmente, usaría un chan Stuff para pasar los resultados nuevamente, luego giraría las rutinas de computación para calcular un Stuff completo y lo devolvería. Si necesita varias partes de una sola Stuff calculada al mismo tiempo, puede generar goroutines de cada goroutine, utilizando canales dedicados. Una vez que haya recopilado todos los resultados, puede (opcionalmente) ordenar la porción con los valores acumulados.

Ejemplo de lo que quiero decir a continuación (podría, en principio, usar una sync.WaitGroup para coordinar las cosas, pero si se conoce el recuento de entrada, no es estrictamente necesario).

type Stuff struct { number int64 double int64 square int64 } // Compute a Stuff with individual computations in-line, send it out func computeStuff(n int64, out chan<- Stuff) { rv := Stuff{number: n} rv.double = n * 2 rv.square = n * n out <- rv } // Compute a Stuff with individual computations concurrent func computeStuffConcurrent(n int64, out chan<- Stuff) { rv := Stuff{number: n} dc := make(chan int64) sc := make(chan int64) defer close(dc) defer close(sc) go double(n, dc) go square(n, sc) rv.double = <-dc rv.square = <-sc out <- rv } func double(n int64, result chan<- int) { result <- n * 2 } func square(n int64, result chan<- int) { result <- n * n } func main() { inputs := []int64{1, 2, 3} results := []Stuff{} resultChannel := make(chan Stuff) for _, input := range inputs { go computeStuff(input, resultChannel) // Or the concurrent version, if the extra performance is needed } for c := 0; c < len(inputs); c++ { results = append(results, <- resultChannel) } // We now have all results, sort them if you need them sorted }