threads golang close go

golang - Varios goroutines escuchando en un canal



golang close (5)

Tengo varios goroutines tratando de recibir en el mismo canal al mismo tiempo. Parece que la última goroutine que comienza a recibir en el canal obtiene el valor. ¿Está esto en alguna parte de la especificación del lenguaje o es un comportamiento indefinido?

c := make(chan string) for i := 0; i < 5; i++ { go func(i int) { <-c c <- fmt.Sprintf("goroutine %d", i) }(i) } c <- "hi" fmt.Println(<-c)

Salida:

goroutine 4

Ejemplo en el patio de recreo

EDITAR:

Me di cuenta de que es más complicado de lo que pensaba. El mensaje pasa por todos los goroutines.

c := make(chan string) for i := 0; i < 5; i++ { go func(i int) { msg := <-c c <- fmt.Sprintf("%s, hi from %d", msg, i) }(i) } c <- "original" fmt.Println(<-c)

Salida:

original, hi from 0, hi from 1, hi from 2, hi from 3, hi from 4

Ejemplo en el patio de recreo


Última respuesta, pero espero que esto ayude a otros en el futuro, como Long Polling, botón "Global", transmitido a todo el mundo.

Effective Go explica el problema:

Los receptores siempre bloquean hasta que haya datos para recibir.

Eso significa que no puedes tener más de 1 goroutine escuchando 1 canal y esperar que TODOS los goroutines reciban el mismo valor.

Ejecute este ejemplo de código .

package main import "fmt" func main() { c := make(chan int) for i := 1; i <= 5; i++ { go func(i int) { for v := range c { fmt.Printf("count %d from goroutine #%d/n", v, i) } }(i) } for i := 1; i <= 25; i++ { c<-i } close(c) }

No verá el "contador 1" más de una vez aunque haya 5 goroutines escuchando el canal. Esto se debe a que cuando la primera goroutine bloquea el canal, todos los demás goroutines deben esperar en línea. Cuando el canal está desbloqueado, el recuento ya se ha recibido y eliminado del canal, por lo que el siguiente goroutine en línea obtiene el siguiente valor de recuento.


Es complicado.

Además, vea qué sucede con GOMAXPROCS = NumCPU+1 . Por ejemplo,

package main import ( "fmt" "runtime" ) func main() { runtime.GOMAXPROCS(runtime.NumCPU() + 1) fmt.Print(runtime.GOMAXPROCS(0)) c := make(chan string) for i := 0; i < 5; i++ { go func(i int) { msg := <-c c <- fmt.Sprintf("%s, hi from %d", msg, i) }(i) } c <- ", original" fmt.Println(<-c) }

Salida:

5, original, hi from 0, hi from 4

Y, mira qué pasa con los canales almacenados en el búfer. Por ejemplo,

package main import "fmt" func main() { c := make(chan string, 5+1) for i := 0; i < 5; i++ { go func(i int) { msg := <-c c <- fmt.Sprintf("%s, hi from %d", msg, i) }(i) } c <- "original" fmt.Println(<-c) }

Salida:

original

Deberías poder explicar estos casos también.


Estudié soluciones existentes y creé una biblioteca de difusión simple https://github.com/grafov/bcast .

group := bcast.NewGroup() // you created the broadcast group go bcast.Broadcasting(0) // the group accepts messages and broadcast it to all members member := group.Join() // then you join member(s) from other goroutine(s) member.Send("test message") // or send messages of any type to the group member1 := group.Join() // then you join member(s) from other goroutine(s) val := member1.Recv() // and for example listen for messages


Para múltiples goroutine escuchar en un canal, sí, es posible. el punto clave es el mensaje en sí, puedes definir un mensaje como ese:

package main import ( "fmt" "sync" ) type obj struct { msg string receiver int } func main() { ch := make(chan *obj) // both block or non-block are ok var wg sync.WaitGroup receiver := 25 // specify receiver count sender := func() { o := &obj { msg: "hello everyone!", receiver: receiver, } ch <- o } recv := func(idx int) { defer wg.Done() o := <-ch fmt.Printf("%d received at %d/n", idx, o.receiver) o.receiver-- if o.receiver > 0 { ch <- o // forward to others } else { fmt.Printf("last receiver: %d/n", idx) } } go sender() for i:=0; i<reciever; i++ { wg.Add(1) go recv(i) } wg.Wait() }

La salida es aleatoria:

5 received at 25 24 received at 24 6 received at 23 7 received at 22 8 received at 21 9 received at 20 10 received at 19 11 received at 18 12 received at 17 13 received at 16 14 received at 15 15 received at 14 16 received at 13 17 received at 12 18 received at 11 19 received at 10 20 received at 9 21 received at 8 22 received at 7 23 received at 6 2 received at 5 0 received at 4 1 received at 3 3 received at 2 4 received at 1 last receiver 4


Sí, es complicado, pero hay un par de reglas generales que deberían hacer que las cosas se sientan mucho más directas.

  • Prefiere usar argumentos formales para los canales que pasa a go-rutinas en lugar de acceder a los canales en el ámbito global. Puede obtener más compiladores revisando de esta manera, y mejor modularidad también.
  • evite leer y escribir en el mismo canal en una rutina específica (incluida la principal). De lo contrario, el punto muerto es un riesgo mucho mayor.

Aquí hay una versión alternativa de su programa, aplicando estas dos pautas. Este caso demuestra muchos escritores y un lector en un canal:

c := make(chan string) for i := 1; i <= 5; i++ { go func(i int, co chan<- string) { for j := 1; j <= 5; j++ { co <- fmt.Sprintf("hi from %d.%d", i, j) } }(i, c) } for i := 1; i <= 25; i++ { fmt.Println(<-c) }

http://play.golang.org/p/quQn7xePLw

Crea las cinco rutinas de ir escribiendo en un solo canal, cada una escribiendo cinco veces. La rutina principal de Go lee los veinticinco mensajes, puede observar que el orden en el que aparecen a menudo no es secuencial (es decir, la simultaneidad es evidente).

Este ejemplo demuestra una característica de los canales Go: es posible tener múltiples escritores compartiendo un canal; Ir intercalará los mensajes automáticamente.

Lo mismo aplica para un escritor y lectores múltiples en un canal, como se ve en el segundo ejemplo aquí:

c := make(chan int) var w sync.WaitGroup w.Add(5) for i := 1; i <= 5; i++ { go func(i int, ci <-chan int) { j := 1 for v := range ci { time.Sleep(time.Millisecond) fmt.Printf("%d.%d got %d/n", i, j, v) j += 1 } w.Done() }(i, c) } for i := 1; i <= 25; i++ { c <- i } close(c) w.Wait()

Este segundo ejemplo incluye una espera impuesta en la goroutine principal, que de otro modo saldría rápidamente y provocaría que los otros cinco goroutines finalizaran temprano (gracias a olov por esta corrección) .

En ambos ejemplos, no se necesitó almacenamiento en búfer. En general, es un buen principio para ver el almacenamiento en búfer como un potenciador de rendimiento solamente. Si su programa no se bloquea sin búferes, tampoco se bloqueará con búferes (pero lo contrario no siempre es cierto). Entonces, como otra regla general, comience sin almacenar en el búfer y luego agréguela más tarde según sea necesario .