Go: Channels
Channels sind eines dieser Dinge, die mir in Go besonders gefallen. Wenn man die handvoll Konzepte, die dazugehören, verstanden hat, dann lässt sich damit die Synchronisation und Kommunikation zwischen Goroutinen super einfach und performant behandeln.
Das Erstellen eines Channels passiert mit make. Zusätzlich wird der Typ angegeben, der im Channel kommuniziert wird. Die letzte Entscheidung bei der Erstellung eines Channels ist, ob dieser buffered oder unbuffered sein soll.
Ein unbuffered Channel blockiert den Sender so lange, bis ein Empfänger die Nachricht entgegennimmt. Beim buffered Channel kann im Vergleich dazu eine Anzahl von Nachrichten angegeben werden, die zwischengespeichert werden können. Hierbei wird der Sender erst blockiert, wenn dieser Zwischenspeicher voll ist. Sobald ein Empfänger einen Slot aus dem Speicher freigibt, kann der Sender wieder Nachrichten in den Channel senden.
// unbuffered Channel
channel := make(chan int)
// buffered Channel
channel := make(chan int, 10)
Um eine Nachricht in den Channel zu senden, wird die spezielle <- Syntax verwendet:
channel <- 10
Zum Auslesen eines Wertes wird die gleiche Syntax, nur verkehrt verwendet:
val, ok := <-channel
Hier findet eine Blockierung an der Stelle im Code statt, bis der Channel entweder einen Wert erhält, oder der Channel mit close geschlossen wurde. In diesem Fall hat ok den Wert false.
close(channel)
Über einen Channel kann mit for range auch iteriert werden:
for val := range channel {
fmt.Println(val)
}
Die Schleife bleibt so lange aktiv (und blockiert), bis der Channel geschlossen wurde.
Mit dem Schlüsselwort select ist es möglich, auf mehrere Channels gleichzeitig zu warten:
select {
case val, ok := <-channel1:
fmt.Println(val)
fmt.Println(ok)
case val, ok := <-channel2:
fmt.Println(val)
fmt.Println(ok)
}
Dies kann auch in einer for Schleife gemacht werden:
for {
select {
case val, ok := <-channel1:
fmt.Println(val)
if !ok {
return
}
case val, ok := <-channel2:
fmt.Println(val)
if !ok {
return
}
}
}
Alle lesenden Zugriffe aus den oberen Beispielen führen zu einer Blockierung, so lange keine neuen Nachrichten im Channel sind oder dieser geschlossen wurde. Es gibt allerdings noch eine Möglichkeit, mit der der Code weiterläuft, wenn keiner dieser Fälle vorhanden ist. Dies wird ebenfalls mit dem select Schlüsselwort und einem default case gemacht:
for {
select {
case val, ok := <-channel1:
fmt.Println(val)
if !ok {
return
}
case val, ok := <-channel2:
fmt.Println(val)
if !ok {
return
}
}
default:
fmt.Println("nichts Neues ...")
}
Channels können bei den Parametern einer Funktion auf drei Arten verwendet werden:
// es können nur Nachrichten versandt werden
func senderOnly(channel chan<- int) {
channel <- 10
}
// es können nur Nachrichten empfangen werden
func receiverOnly(channel <-chan int) {
<- channel
}
// es können sowohl Nachrichten gesendet als auch empfangen werden
func senderAndReceiver(channel chan int) {
channel <- 10
<- channel
}