Um eine Endlosschleife für die Ausführung von zwei Goroutinen zu starten, kann ich den folgenden Code verwenden:
Nach Erhalt der Nachricht wird eine neue Goroutine gestartet und für immer fortgesetzt.
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)
}
}
Ich hätte jetzt gerne das gleiche Verhalten für N Goroutinen, aber wie wird die select-Anweisung in diesem Fall aussehen?
Dies ist das Codebit, mit dem ich begonnen habe, aber ich bin verwirrt, wie die select-Anweisung codiert wird
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)
}
}
Antworten:
Sie können dies mit der
Select
Funktion aus dem Reflect- Paket tun :Sie übergeben ein Array von
SelectCase
Strukturen, die den Kanal identifizieren, auf dem ausgewählt werden soll, die Richtung der Operation und einen Wert, der im Fall einer Sendeoperation gesendet werden soll.Sie könnten also so etwas tun:
Sie können hier mit einem ausführlicheren Beispiel experimentieren: http://play.golang.org/p/8zwvSk4kjx
quelle
Sie können dies erreichen, indem Sie jeden Kanal in eine Goroutine einschließen, die Nachrichten an einen gemeinsam genutzten "aggregierten" Kanal "weiterleitet". Beispielsweise:
Wenn Sie wissen möchten, von welchem Kanal die Nachricht stammt, können Sie sie in eine Struktur mit zusätzlichen Informationen einschließen, bevor Sie sie an den Gesamtkanal weiterleiten.
In meinen (eingeschränkten) Tests ist diese Methode mit dem Reflect-Paket sehr leistungsfähig:
Benchmark-Code hier
quelle
b.N
innerhalb eines Benchmarks durchführen. Andernfalls sind die Ergebnisse (dieb.N
in Ihrer Ausgabe durch 1 und 2000000000 geteilt werden ) völlig bedeutungslos.reflect.Select
Ansatz) ist, dass die Goroutinen, die den Zusammenführungspuffer ausführen, auf jedem Kanal, der zusammengeführt wird, mindestens einen einzelnen Wert haben. Normalerweise ist das kein Problem, aber in einigen spezifischen Anwendungen kann dies ein Deal Breaker sein :(.Um einige Kommentare zu früheren Antworten zu erweitern und hier einen klareren Vergleich zu ermöglichen, ist ein Beispiel für beide bisher vorgestellten Ansätze bei gleicher Eingabe, eine Reihe von Kanälen zum Lesen und eine Funktion zum Aufrufen jedes Werts, der auch wissen muss, welcher Kanal, von dem der Wert kam.
Es gibt drei Hauptunterschiede zwischen den Ansätzen:
Komplexität. Obwohl es teilweise eine Leserpräferenz sein mag, finde ich den Kanalansatz idiomatischer, unkomplizierter und lesbarer.
Performance. Auf meinem Xeon amd64-System führen die Goroutinen + Kanäle aus die Reflexionslösung um etwa zwei Größenordnungen aus (im Allgemeinen ist die Reflexion in Go oft langsamer und sollte nur verwendet werden, wenn dies unbedingt erforderlich ist). Natürlich kann dieser Leistungsunterschied leicht unbedeutend werden, wenn entweder die Funktion, die die Ergebnisse verarbeitet, oder das Schreiben von Werten in die Eingangskanäle erheblich verzögert wird.
Semantik blockieren / puffern. Die Bedeutung davon hängt vom Anwendungsfall ab. Meistens spielt es entweder keine Rolle oder die geringfügige zusätzliche Pufferung in der Goroutine-Zusammenführungslösung kann für den Durchsatz hilfreich sein. Wenn es jedoch wünschenswert ist, die Semantik zu haben, dass nur ein einzelner Writer entsperrt wird und sein Wert vollständig verarbeitet wird, bevor ein anderer Writer entsperrt wird, kann dies nur mit der Reflect-Lösung erreicht werden.
Beachten Sie, dass beide Ansätze vereinfacht werden können, wenn entweder die "ID" des sendenden Kanals nicht erforderlich ist oder wenn die Quellkanäle niemals geschlossen werden.
Goroutine-Zusammenführungskanal:
Reflexionsauswahl:
[Vollständiger Code auf dem Go-Spielplatz .]
quelle
select
oder kannreflect.Select
. Die Goroutinen drehen sich so lange, bis sie alles aus den Kanälen verbrauchen. Es gibt also keinen klaren Weg, den Sie machen könntenProcess1
vorzeitig . Es besteht auch die Möglichkeit von Problemen, wenn Sie mehrere Lesegeräte haben, da die Goroutinen ein Element aus jedem der Kanäle puffern, was bei nicht der Fall istselect
.select
innerhalb einerfor
Schleife benötigenfor range
. Process2 müsste einen anderen Fall einsteckencases
diesen Wert von und ihn speziell behandelni
.Warum würde dieser Ansatz nicht funktionieren, wenn jemand Ereignisse sendet?
quelle
select
bei mehreren Kanälen (ohnedefault
Klausel) ist, dass effizient gewartet wird, bis mindestens einer bereit ist, ohne sich zu drehen.Möglicherweise einfachere Option:
Anstatt ein Array von Kanälen zu haben, sollten Sie nur einen Kanal als Parameter an die Funktionen übergeben, die auf separaten Goroutinen ausgeführt werden, und dann den Kanal in einer Consumer-Goroutine anhören.
Auf diese Weise können Sie nur einen Kanal in Ihrem Listener auswählen, um eine einfache Auswahl zu ermöglichen und die Erstellung neuer Goroutinen zu vermeiden, um Nachrichten aus mehreren Kanälen zusammenzufassen.
quelle