Asynchrone Programmierung in Go: Vollständiger Leitfaden mit Beispielen

Online Python-Trainer für Anfänger

Lernen Sie Python einfach ohne theoretische Überlastung. Lösen Sie praktische Aufgaben mit automatischer Überprüfung und schreiben Sie Code direkt im Browser.

Kurs starten

Einleitung: Warum asynchrone Programmierung in Go?



Go (Golang) wurde von Anfang an für effiziente Arbeit mit Nebenläufigkeit (Concurrency) konzipiert. Im Gegensatz zu vielen Sprachen, in denen Asynchronität durch komplexe Callbacks oder Promises realisiert wird, bietet Go ein elegantes Modell auf Basis von Goroutinen (goroutines) und Kanälen (channels). Dieses Modell ermöglicht es, asynchronen Code zu schreiben, der sich wie synchroner Code liest, aber parallel ausgeführt wird und die Ressourcen von Mehrkernprozessoren maximal nutzt.



In diesem Leitfaden werden wir alle wichtigen Konzepte der asynchronen Programmierung in Go durchgehen: von der Erstellung von Goroutinen bis hin zu komplexen Synchronisationsmustern mit Kanälen und Primitiven aus dem Paket sync. Das Material ist sowohl für Anfänger, die Go gerade erst lernen, als auch für erfahrene Entwickler nützlich, die ihr Wissen systematisieren möchten.



1. Goroutinen (Goroutines): Leichtgewichtige Threads



Eine Goroutine ist ein leichtgewichtiger Ausführungs-Thread, der von der Go-Laufzeitumgebung verwaltet wird. Im Gegensatz zu Betriebssystem-Threads haben Goroutinen einen winzigen Stack (beginnend mit einigen KB), der dynamisch wachsen und schrumpfen kann. Dies ermöglicht es, Zehntausende von Goroutinen gleichzeitig auszuführen, ohne nennenswerten Speicher zu verbrauchen.



Starten einer Goroutine

Um eine Funktion als Goroutine zu starten, fügt man einfach das Schlüsselwort go vor dem Funktionsaufruf hinzu:



package main

import ( "fmt" "time")

func printNumbers() { for i := 1; i <= 5; i++ { fmt.Println(i) time.Sleep(100 * time.Millisecond) }}

func main() { // Starte die Funktion als Goroutine go printNumbers()

// Die Haupt-Goroutine arbeitet weiter fmt.Println("Haupt-Goroutine läuft")

// Gebe der Kind-Goroutine Zeit zur Ausführung time.Sleep(1 * time.Second) fmt.Println("Programm beendet")}


Wichtig: Das Programm endet, wenn die Funktion main() endet. Wenn die Haupt-Goroutine früher fertig ist, werden alle Kind-Goroutinen zwangsweise gestoppt. Daher verwenden wir im Beispiel time.Sleep zum Warten – in der Praxis werden für die Synchronisation Kanäle oder sync.WaitGroup verwendet.



Anonyme Funktionen in Goroutinen

Goroutinen werden oft mit anonymen Funktionen erstellt:



go func() {    fmt.Println("Wird in einer Goroutine ausgeführt")}()


2. Kanäle (Channels): Kommunikation zwischen Goroutinen



Kanäle sind typisierte "Röhren", durch die Goroutinen Werte senden und empfangen können. Dies ist der primäre Mechanismus für Synchronisation und Datenaustausch in Go. Kanäle ermöglichen sicheren Datenzugriff ohne explizite Sperren (Mutexe).



Erstellen und Verwenden eines Kanals

Ein Kanal wird mit der Funktion make() erstellt. Der Operator <- wird zum Senden und Empfangen von Daten verwendet:



package main

import "fmt"

func sum(s []int, c chan int) { total := 0 for _, v := range s { total += v } c <- total // sende das Ergebnis an den Kanal}

func main() { s := []int{7, 2, 8, -9, 4, 0}

c := make(chan int) go sum(s[:len(s)/2], c) go sum(s[len(s)/2:], c)

x, y := <-c, <-c // empfange Daten aus dem Kanal

fmt.Println(x, y, x+y)}


In diesem Beispiel starten wir zwei Goroutinen, die jeweils die Summe einer Hälfte des Slices berechnen. Die Ergebnisse werden an den Kanal gesendet, und die Haupt-Goroutine empfängt sie und gibt die Gesamtsumme aus.



Gepufferte Kanäle

Standardmäßig sind Kanäle ungepuffert – das Senden blockiert, bis der Empfänger bereit ist, den Wert anzunehmen. Gepufferte Kanäle haben eine Warteschlange mit einer bestimmten Größe:



ch := make(chan int, 2) // Kanal mit einem Puffer für 2 Elementech <- 1ch <- 2// ch <- 3 // Fehler: Deadlock, Puffer ist vollfmt.Println(<-ch) // 1fmt.Println(<-ch) // 2

Blogs

Buchempfehlungen