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
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
Ú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 .