Eine Goroutine ist ein leichtgewichtiger Ausführungs-Thread, der vom Go-Runtime verwaltet wird. Um eine neue Goroutine zu starten, wird das Schlüsselwort go vor dem Funktionsaufruf verwendet. Im Hintergrund wird eine Struktur erstellt, die den Stack und den Status der Aufgabe beschreibt, die in die Warteschlange des Goroutine-Schedulers eingefügt wird.
Im Gegensatz zu OS-Threads hat eine Goroutine einen viel kleineren Anfangs-Stack (normalerweise 2 KB), und der Stack wird bei Bedarf automatisch erweitert. Der Go-Scheduler weist sie automatisch den verfügbaren Betriebssystem-Threads zu (M:N-Modell).
Die wichtigsten Unterschiede zu Threads:
package main import ( "fmt" "time" ) func worker(id int, jobs <-chan int, results chan<- int) { for j := range jobs { fmt.Printf("worker %d started job %d\n", id, j) time.Sleep(time.Second) fmt.Printf("worker %d finished job %d\n", id, j) results <- j * 2 } } func main() { jobs := make(chan int, 5) results := make(chan int, 5) for w := 1; w <= 3; w++ { go worker(w, jobs, results) } for j := 1; j <= 5; j++ { jobs <- j } close(jobs) for a := 1; a <= 5; a++ { <-results } }
Können Goroutinen in Go parallel auf mehreren Kernen ausgeführt werden?
Häufig wird die falsche Antwort gegeben: „Nein, weil Go grüne Threads verwendet“. Tatsächlich kann Go mit Hilfe einer Umgebungsvariable oder dem Aufruf runtime.GOMAXPROCS(n) die Ausführung von Goroutinen auf allen verfügbaren Prozessorkernen parallelisieren.
import "runtime" func main() { runtime.GOMAXPROCS(4) // Ermöglicht die Verwendung von 4 Kernen ... }
Geschichte
In einem Projekt eines Backend-Dienstes in Go wurde ein Worker-Pool über Goroutinen implementiert, aber die Programmierer vergaßen, die Anzahl der gleichzeitig ausgeführten Goroutinen zu begrenzen. Infolgedessen startete die Anwendung unter erhöhter Last Tausende von Goroutinen, was zu einem Speichermangel und einem Absturz des Dienstes führte. Das Problem wurde durch Festlegen eines Limits für aktive Goroutinen (zum Beispiel mit einem Semaphore oder einem Worker-Pool) gelöst.
Geschichte
Einer der Mitarbeiter synchronisierte die Daten zwischen Goroutinen falsch, indem er einfache globale Variablen ohne Mutexe oder Kanäle verwendete. Dies führte zu einem Datenrennen (race condition), wodurch sporadisch Fehler bei der Zahlungsabwicklung auftraten. Das Problem wurde erst nach dem Start in der Produktion entdeckt.
Geschichte
In einem Parsing-Service wurde der Zeitpunkt der Übergabe von nil-Kanälen in select übersehen: nach dem Schließen des Kanals wartete select weiterhin blockiert auf Daten, und einige Goroutinen „hängten“. Dies wurde behoben, indem nil dem geschlossenen Kanal zugewiesen und der select korrekt behandelt wurde.