# 2.7 Nebenläufigkeit Es wird behauptet, Go sei das C des 21. Jahrhunderts. Ich glaube, dafür gibt es zwei Gründe: erstens, Go ist eine simple Programmiersprache; zweitens: Nebenläufigkeit (Concurrency im Englischen) ist ein heißes Thema in der heutigen Welt und Go unterstützt die Eigenschaft als ein zentraler Aspekt der Sprache. ## Goroutinen Goroutinen und Nebenläufigkeit sind zwei wichtige Komponenten im Design von Go. Sie ähneln Threads, funktionieren aber auf eine andere Weise. Ein dutzend Goroutinen haben vielleicht nur fünf oder sechs zugrundeliegende Threads. Des Weiteren unterstützt Go vollständig das Teilen von Speicherressourcen zwischen den Goroutinen. Eine Goroutine nimmt gewöhnlicherweise etwa 4 bis 5 KB Speicher ein. Daher ist es nicht schwer, tausende von Goroutinen auf einem einzelnen Computer zu nutzen. Goroutinen sind weniger ressourcenhungrig, effizienter und geeigneter als Systemthreads. Goroutinen laufen im Thread Manager während der Laufzeit von Go. Wir nutzen das Schlüsselwort `go`, um eine neue Goroutine zu erstellen, wobei es sich eigentlich um eine interne Funktion von Go handelt ( ***main() ist ebenfalls eine Goroutine*** ). go Hallo(a, b, c) Schauen wir uns ein Beispiel an. package main import ( "fmt" "runtime" ) func sag(s string) { for i := 0; i < 5; i++ { runtime.Gosched() fmt.Println(s) } } func main() { go sag("Welt") // Erzeugt eine neue Goroutine sag("Hallo") // Aktuelle Goroutine } Ausgabe: Hallo Welt Hallo Welt Hallo Welt Hallo Welt Hallo Wie es scheint, ist es sehr einfach, Nebenläufigkeit in Go durch das Schlüsselwort `go` zu nutzen. Im oberen Beispiel teilen sich beide Goroutinen den selben Speicher. Aber es wäre besser, diesem Rat folge zu leisten: Nutze keine geteilten Daten zur Kommunikation, sondern kommuniziere die geteilten Daten. `runtime.Gosched()` bedeutet, das die CPU andere Goroutinen ausführen und nach einiger Zeit an den Ausgangspunkt zurückkehren soll. Das Steuerungsprogramm nutzt einen Thread, um alle Goroutinen auszuführen. Das bedeutet, dass einzig dort Nebenläufigkeit implementiert wird. Möchtest Du mehr Rechnenkerne im Prozessor nutzen, um die Vorteile paralleler Berechnungen einzubringen, musst Du `runtime.GOMAXPROCS(n)` aufrufen, um die Anzahl der Rechenkerne festzulegen. Gilt `n<1`, verändert sich nichts. Es könnte sein, dass diese Funktion in Zukunft entfernt wird. Für weitere Informationen zum verteilten Rechnen und Nebenläufigkeit findest Du in diesem [Artikel](http://concur.rspace.googlecode.com/hg/talk/concur.html#landing-slide). ## Channels Goroutinen werden im selben Adressraum des Arbeitsspeichers ausgeführt, sodass Du in den Goroutinen die genutzen Ressourcen synchronisieren musst, wenn diese geteilt werden sollen. Aber wie kommuniziere ich zwischen verschiedenen Goroutinen? Hierfür nutzt Go einen sehr guten Mechanismus mit dem Namen `channel`. `channel` ist wie eine bidirektionale Übertragungsleitung (Pipe) in Unix-Shells: nutze `channel` um Daten zu senden und zu empfangen. Der einzige Datentyp, der in Kombination mit diesen Datenkanälen genutzt werden kann, ist der Typ `channel` und das Schlüsselwort `chan`. Beachte, dass Du `make` brauchst, um einen neuen `channel` zu erstellen. ci := make(chan int) cs := make(chan string) cf := make(chan interface{}) `channel` nutzt den Operator `<-`, um Daten zu senden und zu empfangen. ch <- v // Sende v an den Kanal ch. v := <-ch // Empfange Daten von ch und weise sie v zu Schauen wir uns weitere Beispiele an. package main import "fmt" func summe(a []int, c chan int) { gesamt := 0 for _, v := range a { gesamt += v } c <- gesamt // Sende gesamt an c } func main() { a := []int{7, 2, 8, -9, 4, 0} c := make(chan int) go summe(a[:len(a)/2], c) go summe(a[len(a)/2:], c) x, y := <-c, <-c // Empfange Daten von c fmt.Println(x, y, x + y) } Das Senden und Empfangen von Daten durch die Datenkanäle wird standardmäßig gestoppt, um die Goroutinen einfach synchron zu halten. Mit dem Blocken meine ich, dass eine Goroutine nicht weiter ausgeführt wird, sobald keine Daten mehr von einem `channel` empfangen werden (z.B. `value := <-ch`) und andere Goroutinen keine weiteren Daten über den entsprechenden Kanal senden. Anderseits stoppt die sendende Goroutine solange, bis alle Daten (z.B. `ch<-5`)über den Kanal empfangen wurden. ## Gepufferte Channels Eben habe ich die nicht-gepuffter Datenkanäle vorgestellt. Go unterstützt aber auch gepufferte Channel, die mehr als ein Element speichern können, z.B. `ch := make(chan bool, 4)`. Hier wurde ein Channel mit der Kapazität von vier Booleans erstellt. Mit diesem Datenkanal sind wir in der Lage, vier Elemente zu senden, ohne das die Goroutine stoppt. Dies passiert aber bei dem Versuch, ein fünftes Element zu versenden, ohne das es von einer Goroutine empfangen wird. ch := make(chan type, n) n == 0 ! nicht-gepuffert(stoppt) n > 0 ! gepuffert(nicht gestoppt, sobald n Elemente im Kanal sind) Experimentiere mit dem folgenden Code auf Deinem Computer und verändere die Werte. package main import "fmt" func main() { c := make(chan int, 2) // Setze 2 auf 1 und Du erzeugst einen Laufzeitfehler. Aber 3 ist OK. c <- 1 c <- 2 fmt.Println(<-c) fmt.Println(<-c) } ## Range und Close Wir können `range` in gepufferten Kanlen genauso nutzen, wie mit Slices und Maps. package main import ( "fmt" ) func fibonacci(n int, c chan int) { x, y := 1, 1 for i := 0; i < n; i++ { c <- x x, y = y, x + y } close(c) } func main() { c := make(chan int, 10) go fibonacci(cap(c), c) for i := range c { fmt.Println(i) } } `for i := range c` wird nicht eher mit dem Lesen von Daten aus dem Channel aufhören, ehe dieser geschlossen ist. Wir nutzen das Schlüsselwort `close`, um den Datenkanal im oberen Beispiel zu schließen. Es ist unmöglich, Daten über einen geschlossenen Channel zu senden oder zu empfangen. Mit `v, ok := <-ch` kannst Du den Status eines Kanals überprüfen. Wird `ok` auf false gesetzt, bedeutet dies, dass sich keine weiteren Daten im Channel befinden und er geschlossen wurde. Denke aber immer daran, die Datenkanäle auf seiten der Datenproduzenten zu schließen und nicht bei den Empfängern der Daten. Andernfalls kann es passieren, dass sich Dein Programm in den Panikmodus versetzt. Ein weiterer Aspekt, den wir nicht unterschlagen sollten, ist, dass Du Channels nicht wie Dateien behandeln solltest. Du brauchst sie nicht andauernd schließen, sondern erst, wenn Du sicher bist, dass sie nicht mehr gebraucht werden oder Du das Abfragen der übertragenen Daten mit dem Schlüsselwort `range` beenden willst. ## Select In den vorherigen Beispielen haben wir bisher immer nur einen Datenkanal verwendet, aber wie können wir Gebrauch von mehreren Channels machen? Go erlaubt es, mit dem Schlüsselwort `select` viele Kanäle nach Daten zu belauschen. `select` stoppt standardmäßig eine Goroutine und wird einzig ausgeführt, wenn einer der Channels Daten sendet oder empfängt. Sollten mehrere Kanäle zur gleichen Zeit aktiv sein, wird ein zufälliger ausgewählt. package main import "fmt" func fibonacci(c, quit chan int) { x, y := 1, 1 for { select { case c <- x: x, y = y, x + y case <-quit: fmt.Println("Fertig") return } } } func main() { c := make(chan int) quit := make(chan int) go func() { for i := 0; i < 10; i++ { fmt.Println(<-c) } quit <- 0 }() fibonacci(c, quit) } `select` hat ebenfalls einen Standardfall wie `switch`, mit dem Namen `default`. Wenn kein Datenkanal aktiv sein sollte, wird der Standardfall ausgeführt (es wird auf keinen Kanal mehr gewartet). select { case i := <-c: // Benutze i default: // Dieser Code wird ausgeführt, sollte c gestoppt worden sein } ## Zeitüberschreitung Manchmal kann es vorkommen, dass eine Goroutine gestoppt wird. Wie können wir verhindern, dass daraus resultierend das gesamte Programm aufhört zu arbeiten? Es ist ganz einfach. Es muss lediglich eine Zeitüberschreitung in `select` festgelegt werden. func main() { c := make(chan int) o := make(chan bool) go func() { for { select { case v := <- c: println(v) case <- time.After(5 * time.Second): println("Zeitüberschreitung") o <- true break } } }() <- o } ## Runtime goroutine Das Paket `runtime` beinhaltet ein paar Funktionen zum Umgang mit Goroutinen. - `runtime.Goexit()` Verlässt die aktuelle Goroutine, aber verzögerte Funktionen werden wie gewohnt ausgeführt. - `runtime.Gosched()` Lässt die CPU vorerst andere Goroutinen ausführen und kehrt nach einiger Zeit zum Ausgangspunkt zurück. - `runtime.NumCPU() int` Gibt die Anzahl der Rechenkerne zurück. - `runtime.NumGoroutine() int` Gibt die Anzahl der Goroutinen zurück. - `runtime.GOMAXPROCS(n int) int` Legt die Anzahl der Rechenkerne fest, die benutzt werden sollen. ## Links - [Inhaltsverzeichnis](preface.md) - Vorheriger Abschnitt: [Interfaces](02.6.md) - Nächster Abschnitt: [Zusammenfassung](02.8.md)