para iniciar um loop infinito de execução de duas goroutines, posso usar o código abaixo:
após receber a mensagem, ele iniciará uma nova goroutine e continuará para sempre.
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)
}
}
Agora, gostaria de ter o mesmo comportamento para N gorotinas, mas como ficará a instrução select nesse caso?
Este é o bit de código com o qual comecei, mas estou confuso sobre como codificar a instrução select
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)
}
}
Respostas:
Você pode fazer isso usando a
Select
função do pacote reflet :Você passa uma matriz de
SelectCase
estruturas que identificam o canal a ser selecionado, a direção da operação e um valor a ser enviado no caso de uma operação de envio.Então você poderia fazer algo assim:
Você pode experimentar um exemplo mais detalhado aqui: http://play.golang.org/p/8zwvSk4kjx
fonte
Você pode fazer isso envolvendo cada canal em uma goroutine que "encaminha" mensagens para um canal "agregado" compartilhado. Por exemplo:
Se você precisa saber de qual canal a mensagem se originou, pode envolvê-la em uma estrutura com qualquer informação extra antes de encaminhá-la para o canal agregado.
No meu teste (limitado), este método tem um desempenho muito melhor usando o pacote reflet:
Código de referência aqui
fonte
b.N
dentro de um benchmark. Caso contrário, os resultados (que são divididos porb.N
1 e 2000000000 em sua saída) serão completamente sem sentido.reflect.Select
abordagem) é que os goroutines realizam o buffer de mesclagem, no mínimo, um único valor em cada canal sendo mesclado. Normalmente, isso não será um problema, mas em algumas aplicações específicas que podem ser um obstáculo :(.Para expandir alguns comentários sobre as respostas anteriores e fornecer uma comparação mais clara, aqui está um exemplo de ambas as abordagens apresentadas até agora com a mesma entrada, uma fatia de canais para ler e uma função para chamar para cada valor que também precisa saber qual canal de onde veio o valor.
Existem três diferenças principais entre as abordagens:
Complexidade. Embora possa ser parcialmente uma preferência do leitor, acho a abordagem do canal mais idiomática, direta e legível.
Atuação. No meu sistema Xeon amd64, o goroutines + channels executa a solução de reflexão em cerca de duas ordens de magnitude (em geral, a reflexão em Go é frequentemente mais lenta e só deve ser usada quando absolutamente necessário). Obviamente, se houver algum atraso significativo no processamento da função dos resultados ou na gravação dos valores nos canais de entrada, essa diferença de desempenho pode facilmente se tornar insignificante.
Semântica de bloqueio / buffer. A importância disso depende do caso de uso. Na maioria das vezes, isso não importa ou o pequeno buffer extra na solução de mesclagem de goroutine pode ser útil para o rendimento. No entanto, se for desejável ter a semântica de que apenas um único gravador seja desbloqueado e seu valor totalmente gerenciado antes que qualquer outro gravador seja desbloqueado, então isso só pode ser alcançado com a solução de reflexão.
Observe que ambas as abordagens podem ser simplificadas se o "id" do canal de envio não for necessário ou se os canais de origem nunca forem fechados.
Canal de fusão Goroutine:
Seleção de reflexão:
[Código completo no playground Go .]
fonte
select
oureflect.Select
faz. Os goroutines continuarão girando até consumirem tudo dos canais, então não há uma maneira clara de vocêProcess1
sair mais cedo. Também existe o potencial de problemas se você tiver vários leitores, pois os goroutines armazenam em buffer um item de cada um dos canais, o que não acontecerá comselect
.select
em umfor
loop em vez dofor range
loop mais simples usado atualmente. O Processo2 precisaria colocar outro casocases
e lidar com esse valor especiali
.Por que essa abordagem não funcionaria presumindo que alguém está enviando eventos?
fonte
select
em vários canais (sem umadefault
cláusula) é que ele espera com eficiência até que pelo menos um esteja pronto sem girar.Opção possivelmente mais simples:
Em vez de ter uma matriz de canais, por que não passar apenas um canal como parâmetro para as funções que estão sendo executadas em goroutines separadas e, em seguida, ouvir o canal em uma goroutine de consumidor?
Isso permite que você selecione apenas um canal em seu ouvinte, tornando a seleção simples e evitando a criação de novos goroutines para agregar mensagens de vários canais?
fonte