para iniciar un ciclo interminable de ejecución de dos goroutines, puedo usar el siguiente código:
después de recibir el mensaje, se iniciará una nueva goroutine y continuará para siempre.
c1 := make(chan string)
c2 := make(chan string)
go DoStuff(c1, 5)
go DoStuff(c2, 2)
for ; true; {
select {
case msg1 := <-c1:
fmt.Println("received ", msg1)
go DoStuff(c1, 1)
case msg2 := <-c2:
fmt.Println("received ", msg2)
go DoStuff(c2, 9)
}
}
Ahora me gustaría tener el mismo comportamiento para N goroutines, pero ¿cómo se verá la instrucción select en ese caso?
Este es el bit de código con el que comencé, pero estoy confundido sobre cómo codificar la declaración de selección
numChans := 2
//I keep the channels in this slice, and want to "loop" over them in the select statemnt
var chans = [] chan string{}
for i:=0;i<numChans;i++{
tmp := make(chan string);
chans = append(chans, tmp);
go DoStuff(tmp, i + 1)
//How shall the select statment be coded for this case?
for ; true; {
select {
case msg1 := <-c1:
fmt.Println("received ", msg1)
go DoStuff(c1, 1)
case msg2 := <-c2:
fmt.Println("received ", msg2)
go DoStuff(c2, 9)
}
}
Respuestas:
Para ello, puede utilizar la
Select
función de la reflejan paquete:Pasas una matriz de
SelectCase
estructuras que identifican el canal para seleccionar, la dirección de la operación y un valor para enviar en el caso de una operación de envío.Entonces podrías hacer algo como esto:
Puede experimentar con un ejemplo más desarrollado aquí: http://play.golang.org/p/8zwvSk4kjx
fuente
Puede lograr esto envolviendo cada canal en una goroutine que "reenvía" mensajes a un canal "agregado" compartido. Por ejemplo:
Si necesita saber de qué canal se originó el mensaje, puede envolverlo en una estructura con cualquier información adicional antes de reenviarlo al canal agregado.
En mis pruebas (limitadas), este método supera con creces el uso del paquete reflect:
Código de referencia aquí
fuente
b.N
dentro de una referencia. De lo contrario, los resultados (que se dividen entreb.N
1 y 2000000000 en su salida) no tendrán ningún significado.reflect.Select
enfoque) es que las gorutinas hacen el búfer de fusión al mínimo un valor único en cada canal que se fusiona. Por lo general, eso no será un problema, pero en algunas aplicaciones específicas puede ser un factor decisivo :(.Para ampliar algunos comentarios sobre respuestas anteriores y proporcionar una comparación más clara, aquí hay un ejemplo de ambos enfoques presentados hasta ahora con la misma entrada, un segmento de canales para leer y una función para llamar para cada valor que también necesita saber cuál canal del que proviene el valor.
Hay tres diferencias principales entre los enfoques:
Complejidad. Aunque puede ser parcialmente una preferencia del lector, encuentro que el enfoque del canal es más idiomático, directo y legible.
Actuación. En mi sistema Xeon amd64, los canales goroutines + realizan la solución de reflexión en aproximadamente dos órdenes de magnitud (en general, la reflexión en Go suele ser más lenta y solo debe usarse cuando sea absolutamente necesario). Por supuesto, si hay un retraso significativo en la función que procesa los resultados o en la escritura de valores en los canales de entrada, esta diferencia de rendimiento puede fácilmente volverse insignificante.
Semántica de bloqueo / almacenamiento en búfer. La importancia de esto depende del caso de uso. La mayoría de las veces, no importa o el ligero almacenamiento en búfer adicional en la solución de fusión de goroutine puede ser útil para el rendimiento. Sin embargo, si es deseable tener la semántica de que solo se desbloquea un solo escritor y su valor se maneja completamente antes de que se desbloquee cualquier otro escritor, entonces eso solo se puede lograr con la solución reflect.
Tenga en cuenta que ambos enfoques se pueden simplificar si no se requiere el "id" del canal de envío o si los canales de origen nunca se cerrarán.
Canal de fusión de Goroutine:
Selección de reflexión:
[Código completo en el patio de juegos de Go ].
fuente
select
o loreflect.Select
hace. Las gorutinas seguirán girando hasta que consuman todo lo de los canales, por lo que no hay una forma clara de poderProcess1
salir temprano. También existe la posibilidad de que surjan problemas si tiene varios lectores, ya que las goroutines almacenan un elemento de cada uno de los canales, lo que no sucederá conselect
.select
dentro de unfor
ciclo en lugar delfor range
ciclo más simple que se usa actualmente. Process2 necesitaría insertar otro casocases
y un manejo especial de ese valor dei
.¿Por qué este enfoque no funcionaría asumiendo que alguien está enviando eventos?
fuente
select
los canales múltiples (sin unadefault
cláusula) es que espera de manera eficiente hasta que al menos uno esté listo sin girar.Opción posiblemente más sencilla:
En lugar de tener una serie de canales, ¿por qué no pasar solo un canal como parámetro a las funciones que se ejecutan en goroutines separados, y luego escuchar el canal en un goroutine consumidor?
Esto le permite seleccionar un solo canal en su oyente, lo que hace que sea una selección simple y evite la creación de nuevas gorutinas para agregar mensajes de múltiples canales.
fuente