ProgrammierungSenior Go Entwickler

Erzählen Sie, wie Go Closures (func literals/closures) implementiert und welche Einschränkungen und Besonderheiten bei der Verwendung von Closures bestehen: Wo werden sie gespeichert, wie werden Variablen erfasst, wie verhält sich das Verhalten der erfassten Variablen in verschiedenen Szenarien?

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

Antwort

In Go sind anonyme Funktionen (func literals) in der Lage, Closures zu erstellen, das heißt, sie können auf Variablen aus dem umgebenden Gültigkeitsbereich zugreifen, selbst nachdem dieser beendet wurde. Solche Closures belegen Speicher im Heap, wenn dies für eine korrekte Ausführung erforderlich ist (erkannt durch Escape-Analyse).

Beispiel:

func adder() func(int) int { sum := 0 return func(x int) int { sum += x return sum } } a := adder() printf("%d\n", a(5)) // 5 printf("%d\n", a(10)) // 15

Besonderheiten:

  • Ein Closure erfasst Variablen des äußeren Bereichs per Referenz (nicht deren Werte zum Zeitpunkt der Erstellung).
  • Wenn die Variable außerhalb des Closures geändert wird, sieht das Closure den neuen Wert.
  • Wenn ein Closure aus einer Funktion zurückgegeben wird, leben die erfassten Variablen bis zum Lebensende des Closures.
  • Wenn ein Closure nicht verwendet wird, kann die Escape-Analyse ermöglichen, dass die Variablen nicht in den Heap verschoben werden.

Trickfrage

Was gibt dieser Code aus?

func main() { fs := []func(){} for i := 0; i < 3; i++ { fs = append(fs, func() { fmt.Println(i) }) } for _, f := range fs { f() } }

Viele werden sagen, dass 0, 1, 2 ausgegeben wird, jedoch wird das Ergebnis sein:

3
3
3

Alle Closures verweisen auf dieselbe Variable i; nach Abschluss der Schleife beträgt ihr Wert 3.

Richtig: eine Kopie der Variable im Schleifenrumpf erfassen:

for i := 0; i < 3; i++ { v := i // neue Variable fs = append(fs, func() { fmt.Println(v) }) }

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


Geschichte

Im Projekt für dynamisches Routing wurde eine Schleife verwendet, um viele Handler über Closures zu erstellen, jeder sollte seinen eigenen Pfad ergreifen. Infolgedessen druckten alle Handler den letzten Pfad — es wurde keine separate Variable in jedem Closure erstellt. Der Fehler wurde nur bei der Integration mit der HTTP-API entdeckt.


Geschichte

Beim Testen des parallelen Zugriffs über Goroutinen erfasste das Closure innerhalb der Schleife die Referenz auf den Index und nicht eine Kopie. Dies erzeugte "zufällige" Effekte: Daten wurden nicht in ihren eigenen Array-Slot geschrieben, sondern in den letzten.


Geschichte

In einer Funktion zur Statistiksammlung änderte das Closure eine gemeinsame Variable aus dem äußeren Bereich, obwohl der Autor einen unabhängigen Zähler für jede Aufgabe erwartete. Das Problem fiel aufgrund der unangemessen rekonstruierbaren Summe auf, die immer gemeinsam und nicht privat war, trotz lokaler Logik.