ProgrammierungBackend-Entwickler

Erklären Sie, was eine Goroutine in Go ist. Wie funktioniert sie im Hintergrund und wie unterscheidet sie sich von Threads in anderen Sprachen?

Bestehen Sie Vorstellungsgespräche mit dem Hintsage-KI-Assistenten

Antwort

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:

  • Sie werden schneller erstellt und benötigen weniger Speicher
  • Sie werden vom Go-Runtime geplant, nicht vom OS
  • Sie werden automatisch auf die Kerne skaliert
  • Die Synchronisation erfolgt über Kanäle, was viele Arten von Datenrennen verhindert

Beispiel für die Nutzung von Goroutinen und Kanälen:

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 } }

Fangfrage

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.

Beispiel:

import "runtime" func main() { runtime.GOMAXPROCS(4) // Ermöglicht die Verwendung von 4 Kernen ... }

Beispiele für reale Fehler aufgrund von Unkenntnis der Feinheiten des Themas


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.