aby rozpocząć nieskończoną pętlę wykonywania dwóch goroutines, mogę użyć poniższego kodu:
po otrzymaniu wiadomości uruchomi nowy goroutine i będzie trwał wiecznie.
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)
}
}
Chciałbym teraz mieć takie samo zachowanie dla N gorutyn, ale jak będzie wyglądać instrukcja select w tym przypadku?
To jest kod, od którego zacząłem, ale nie mam pojęcia, jak zakodować instrukcję 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)
}
}
Odpowiedzi:
Możesz to zrobić za pomocą
Select
funkcji z pakietu Reflect :Przekazujesz tablicę
SelectCase
struktur, które identyfikują kanał do wybrania, kierunek operacji i wartość do wysłania w przypadku operacji wysyłania.Możesz więc zrobić coś takiego:
Możesz eksperymentować z bardziej rozbudowanym przykładem tutaj: http://play.golang.org/p/8zwvSk4kjx
źródło
Możesz to osiągnąć, opakowując każdy kanał w gorutynę, która „przekazuje” wiadomości do wspólnego kanału „zbiorczego”. Na przykład:
Jeśli chcesz wiedzieć, z którego kanału pochodzi wiadomość, możesz owinąć ją w strukturę z dodatkowymi informacjami przed przekazaniem jej do kanału zbiorczego.
W moich (ograniczonych) testach ta metoda znacznie przewyższa wydajność przy użyciu pakietu Reflect:
Kod testu porównawczego tutaj
źródło
b.N
test porównawczy. W przeciwnym razie wyniki (które są podzielone przezb.N
1 i 2000000000 w wyniku) będą całkowicie bez znaczenia.reflect.Select
podejściem) jest to, że gorutynki wykonują bufor scalający przy co najmniej jednej wartości na każdym łączonym kanale. Zwykle nie stanowi to problemu, ale w niektórych konkretnych zastosowaniach może to być przełomem :(.Aby rozwinąć niektóre komentarze do poprzednich odpowiedzi i zapewnić jaśniejsze porównanie, poniżej przedstawiono przykład obu podejść przedstawionych do tej pory, biorąc pod uwagę te same dane wejściowe, wycinek kanałów, z których można odczytać i funkcję do wywołania dla każdej wartości, która również musi wiedzieć, z której kanał, z którego pochodzi wartość.
Istnieją trzy główne różnice między podejściami:
Złożoność. Chociaż może to być częściowo preferencja czytelnika, uważam, że podejście kanałowe jest bardziej idiomatyczne, proste i czytelne.
Występ. W moim systemie Xeon amd64 goroutines + channel out wykonuje rozwiązanie odbicia o około dwa rzędy wielkości (ogólnie odbicie w Go jest często wolniejsze i powinno być używane tylko wtedy, gdy jest to absolutnie wymagane). Oczywiście, jeśli występuje jakiekolwiek znaczne opóźnienie w funkcji przetwarzającej wyniki lub w zapisywaniu wartości do kanałów wejściowych, ta różnica w wydajności może łatwo stać się nieistotna.
Semantyka blokowania / buforowania. Znaczenie tego zależy od przypadku użycia. Najczęściej albo nie ma to znaczenia, albo niewielkie dodatkowe buforowanie w rozwiązaniu scalającym goroutine może być pomocne dla przepustowości. Jeśli jednak pożądane jest, aby semantyka była odblokowana tylko dla jednego modułu zapisującego, a jego wartość jest w pełni obsługiwana przed odblokowaniem jakiegokolwiek innego modułu zapisującego, można to osiągnąć tylko za pomocą rozwiązania refleksyjnego.
Należy zauważyć, że oba podejścia można uprościć, jeśli „id” kanału wysyłającego nie jest wymagany lub jeśli kanały źródłowe nigdy nie zostaną zamknięte.
Kanał łączenia Goroutine:
Wybierz odbicie:
[Pełny kod na placu zabaw Go .]
źródło
select
lubreflect.Select
robi. Gorutyny będą się obracać, dopóki nie pochłoną wszystkiego z kanałów, więc nie ma jasnego sposobu naProcess1
wcześniejsze wyjście. Istnieje również możliwość wystąpienia problemów, jeśli masz wielu czytelników, ponieważ gorutyny buforują jeden element z każdego z kanałów, co się nie stanieselect
.select
wfor
pętli zamiast prostszejfor range
pętli obecnie używanej. Process2 musiałby umieścić inny przypadekcases
i specjalny sposób obsługiwać tę wartośći
.Dlaczego to podejście nie działałoby przy założeniu, że ktoś wysyła zdarzenia?
źródło
select
na wielu kanałach (bezdefault
klauzuli) polega na tym, że efektywnie czeka, aż przynajmniej jeden będzie gotowy bez wirowania.Prawdopodobnie prostsza opcja:
Zamiast mieć tablicę kanałów, dlaczego nie przekazać tylko jednego kanału jako parametru do funkcji uruchamianych na oddzielnych gorutynach, a następnie słuchać kanału w gorutynach konsumenckich?
Dzięki temu możesz wybierać tylko na jednym kanale w swoim odbiorniku, ułatwiając wybór i unikając tworzenia nowych goroutines w celu agregowania wiadomości z wielu kanałów?
źródło