Parallelität und Multithreading in Go: Ein vollständiger Leitfaden für Entwickler

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

Concurrency und Multithreading in Go: Von Goroutinen bis zu Channels

Die Sprache Go (Golang) wurde bei Google unter Berücksichtigung moderner Anforderungen an Leistung und Skalierbarkeit entwickelt. Ihre Hauptstärke ist die integrierte Unterstützung von Concurrency, die auf Sprachebene und nicht über externe Bibliotheken implementiert ist. Im Gegensatz zu Python, wo Multithreading durch den GIL (Global Interpreter Lock) eingeschränkt ist, bietet Go leichtgewichtige Goroutinen, die es ermöglichen, Tausende von Aufgaben gleichzeitig effizient auszuführen. In diesem Artikel werden wir die wichtigsten Konzepte von Concurrency und Multithreading in Go behandeln, einschließlich Goroutinen, Channels, Mutexe und WaitGroups, mit praktischen Codebeispielen.



1. Goroutinen: Leichtgewichtige Threads in Go

Eine Goroutine ist eine Funktion, die gleichzeitig mit anderen Funktionen im selben Adressraum ausgeführt wird. Sie läuft auf System-Threads, nutzt aber den eigenen Scheduler von Go, was sie deutlich günstiger macht als einen normalen OS-Thread. Eine Goroutine wird mit dem Schlüsselwort go erstellt.



package main

import ( "fmt" "time")

func say(s string) { for i := 0; i < 5; i++ { time.Sleep(100 * time.Millisecond) fmt.Println(s) }}

func main() { go say("Hello") // Goroutine starten say("World") // Normaler Aufruf im Hauptthread}


In diesem Beispiel wird say("Hello") in einer separaten Goroutine ausgeführt, während say("World") im Hauptthread läuft. Die Ausgabe wird gemischt sein, da beide Funktionen gleichzeitig arbeiten. Wichtig zu beachten: Wenn die Hauptfunktion main endet, werden alle Goroutinen zwangsweise gestoppt.



2. Channels: Sichere Datenübertragung zwischen Goroutinen

Channels sind typisierte "Röhren", über die Goroutinen miteinander kommunizieren. Sie sorgen für Synchronisation ohne explizite Verwendung von Mutexen. Channels können gepuffert (mit Kapazität) oder ungepuffert (synchron) sein.



Ungepufferter Channel

package main

import "fmt"

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

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 // Vom Channel empfangen

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


Hier berechnen zwei Goroutinen die Summen der Array-Hälften und senden die Ergebnisse an den Channel. Die Hauptgoroutine blockiert, bis beide Werte empfangen wurden. Ein ungepufferter Channel fungiert als synchroner Punkt: Das Senden blockiert, bis der Empfänger bereit ist, und umgekehrt.



Gepufferter Channel

package main

import "fmt"

func main() { ch := make(chan int, 2) // Puffer für 2 Elemente ch <- 1 ch <- 2 // ch <- 3 // dies würde einen Deadlock verursachen, da der Puffer voll ist fmt.Println(<-ch) fmt.Println(<-ch)}


Ein gepufferter Channel erlaubt das Senden von Daten ohne Blockierung, solange der Puffer nicht voll ist. Dies ist nützlich, wenn die Senderate höher ist als die Verarbeitungsrate.



3. Synchronisation: WaitGroup und Mutexe

Obwohl Channels die meisten Szenarien abdecken, ist manchmal eine feinere Kontrolle über die Synchronisation erforderlich. Dafür bietet das Paket sync die WaitGroup (Warten auf das Ende einer Gruppe von Goroutinen) und Mutex (gegenseitiger Ausschluss zum Schutz gemeinsamer Daten).



WaitGroup: Auf alle Goroutinen warten

package main

import ( "fmt" "sync")

func worker(id int, wg *sync.WaitGroup) { defer wg.Done() // Zähler bei Beendigung verringern fmt.Printf("Worker %d starting", id) // Arbeit simulieren fmt.Printf("Worker %d done", id)}

func main() { var wg sync.WaitGroup

for i := 1; i <= 3; i++ { wg.Add(1) // Zähler erhöhen

Blogs

Buchempfehlungen