Lektionen des Moduls (1/5)
Goroutinen: leichtgewichtiger Parallelismus
Eine Goroutine ist eine Einheit zur gleichzeitigen Ausführung, die vom Go verwaltet wird
Laufzeit. Sie starten eine mit dem Schlüsselwort go vor einer Funktion
Anruf:
go greet("Anna") // chiamata a funzione esistente
go func() { // funzione anonima
fmt.Println("hi")
}()Die go-Anweisung gibt sofort zurück: Die Goroutine beginnt in
parallel und der Anrufer macht weiter.
Warum sie „leicht“ sind
- Anfänglicher Stack ~2 KB (wächst dynamisch, bei Bedarf bis zu Hunderten von MB).
- Multiplex auf eine kleine Anzahl von Betriebssystem-Threads durch die Laufzeit (
GOMAXPROCS). - Tausende/Millionen Goroutinen sind Routine; Mit Betriebssystem-Threads wären sie unmöglich.
Vom Konzept her handelt es sich um „gleichzeitiges Feuern und Vergessen“, aber mit all dem
Idiomatische Tools zur Koordinierung (Kanäle, sync.WaitGroup,
context).
main wartet nicht
func main() {
go func() { fmt.Println("ciao dalla goroutine") }()
// main esce subito: niente garanzia che la goroutine completi
}Wenn main zurückkehrt, wird der Prozess beendet: Goroutinen werden nicht abgerufen
Um „ihre Arbeit zu beenden“, werden sie einfach zusammen mit den getötet
Prozess. Eine explizite Synchronisierung ist erforderlich.
Synchronisierung: eine Vorschau
Die drei Tools, die Sie in den nächsten Lektionen sehen werden:
// 1) Channel: la goroutine "segnala" via canale
done := make(chan struct{})
go func() {
work()
done <- struct{}{}
}()
<-done // attendi
// 2) sync.WaitGroup: N goroutine
var wg sync.WaitGroup
wg.Add(1)
go func() { defer wg.Done(); work() }()
wg.Wait()
// 3) context: cancellazione/timeout propagatiDer „klassische“ Closing-in-Loop-Bug
Bis Go 1.21 kam dies häufig vor:
for i := 0; i < 3; i++ {
go func() { fmt.Println(i) }() // stampa "3 3 3" su versioni vecchie
}Ab Go 1.22 wird die Variable for jeweils neu erstellt
Iteration, sodass der Fehler standardmäßig behoben ist. In älteren Codebasen wird dies der Fall sein
Ich sehe immer noch das Fixmuster:
for i := 0; i < 3; i++ {
i := i // shadow esplicito
go func() { fmt.Println(i) }()
}
// oppure passa come argomento:
for i := 0; i < 3; i++ {
go func(n int) { fmt.Println(n) }(i)
}Goroutine != Betriebssystem-Thread
| Aspekt | Goroutine | Betriebssystem-Thread |
|---|---|---|
| Anfangsstapel | ~2 KB | 1–8 MB (beim Start zugewiesen) |
| Schöpfung | Nanosekunden | Mikrosekunden |
| Terminplanung | Gehen Sie zur Laufzeit (kooperativ) | Kernel (präventiv) |
| Praktische Zählung | 100K+ | ein paar tausend |
Ein einzelner Betriebssystem-Thread kann viele Goroutinen hosten. Die Laufzeit verschiebt sich Goroutinen über Threads hinweg bei Bedarf.
Probieren Sie es aus
Starten Sie die Greet-Funktion als Goroutine und warten Sie 100 ms mit time.Sleep, bevor Sie main verlassen.
Hinweis anzeigen
`go` vor einem Funktionsaufruf startet die Goroutine.
Lösung nach 3 Versuchen verfügbar
Starten Sie eine anonyme Funktion als Goroutine, die „hi“ ausgibt.
Hinweis anzeigen
Eine anonyme Funktion wird mit einem abschließenden `()` aufgerufen: `go func(){...}()`.
Lösung nach 3 Versuchen verfügbar
Was passiert, wenn `main` beendet wird, bevor eine Goroutine abgeschlossen ist?
func main() {
go work()
// main ritorna immediatamente
}Zusammenfassung
go f()startet gleichzeitig f(); es kehrt sofort zurück.- Goroutine = ~2 KB anfänglich, gemultiplext auf einige Betriebssystem-Threads.
mainwartet nicht: Sie benötigen eine Synchronisierung (Kanal/WaitGroup/Kontext).time.Sleepist nur didaktisch, niemals für die Synchronisierung in der Produktion.- Ab Go 1.22 ist die
for-Variable bei jeder Iteration „frisch“ (kein „3 3 3“-Fehler mehr).