Concurrency und Multithreading in Go: Von A bis Z

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 A bis Z



Go (Golang) wurde mit Blick auf moderne Rechenlasten entwickelt. Im Zeitalter von Mehrkernprozessoren und Cloud-Diensten ist die Fähigkeit, effizient mit Concurrency zu arbeiten, zu einer unverzichtbaren Fähigkeit für Backend-Entwickler geworden. In diesem Artikel werden wir untersuchen, wie Go die parallele Ausführung von Code bewältigt, wie sich Goroutinen von OS-Threads unterscheiden und wie man Channels und Mutexe richtig einsetzt.



1. Goroutinen: Leichtgewichtige Threads



Eine Goroutine ist eine Funktion oder Methode, die gleichzeitig mit anderen Goroutinen ausgeführt wird. Im Gegensatz zu Betriebssystem-Threads haben Goroutinen minimale Gemeinkosten: Ihr Stack beginnt bei 2-4 KB und kann dynamisch wachsen. Eine Goroutine zu starten ist unglaublich einfach – fügen Sie einfach das Schlüsselwort go vor dem Funktionsaufruf hinzu.



package main

import ( "fmt" "time")

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

func main() { go say("Hallo von der Goroutine") say("Hallo von main") // Geben Sie der Goroutine Zeit, abzuschließen time.Sleep(500 * time.Millisecond)}


Beachten Sie: Das Programm wird sofort nach der Ausführung der Funktion main() beendet, ohne auf den Abschluss der gestarteten Goroutinen zu warten. Zur Synchronisation verwenden wir time.Sleep (in realen Projekten sync.WaitGroup oder Channels).



2. Channels: Sichere Datenübertragung



Channels sind typisierte "Röhren", über die Goroutinen Daten austauschen. Sie lösen zwei zentrale Aufgaben: die Synchronisation der Ausführung und die Übergabe von Werten. Channels können gepuffert oder ungepuffert sein.



Ungepufferter Channel

Der Sende-Vorgang blockiert die Goroutine, bis eine andere Goroutine einen Lesevorgang durchführt. Dies gewährleistet eine Synchronisation "im laufenden Betrieb".



package main

import "fmt"

func main() { ch := make(chan string)

go func() { ch <- "Hallo von der Goroutine!" }()

msg := <-ch fmt.Println(msg)}


Gepufferter Channel

Ein Channel mit Puffer ermöglicht das Senden mehrerer Werte ohne sofortiges Lesen, bis der Puffer voll ist.



package main

import "fmt"

func main() { ch := make(chan int, 2) ch <- 10 ch <- 20 // ch <- 30 // deadlock! Puffer ist voll

fmt.Println(<-ch) // 10 fmt.Println(<-ch) // 20}


Range und close

Das Schließen eines Channels signalisiert den Empfängern, dass keine weiteren Daten mehr kommen. Die for range-Schleife wird automatisch beendet, wenn der Channel geschlossen wird.



package main

import "fmt"

func producer(ch chan int) { for i := 1; i <= 5; i++ { ch <- i } close(ch)}

func main() { ch := make(chan int) go producer(ch)

for val := range ch { fmt.Println("Erhalten:", val) }}


3. Synchronisation und Mutexe



Trotz der Leistungsfähigkeit von Channels ist manchmal ein direkter Zugriff auf gemeinsame Daten erforderlich. Das Paket sync bietet Mutexe (sync.Mutex) und Reader-Writer-Mutexe (sync.RWMutex) zum Schutz kritischer Abschnitte.



package main

import ( "fmt" "sync")

var counter = 0var mu sync.Mutex

func increment(wg *sync.WaitGroup) { defer wg.Done() mu.Lock() counter++ mu.Unlock()}

func main() { var wg sync.WaitGroup for i := 0; i < 1000; i++ { wg.Add(1) go increment(&wg) } wg.Wait() fmt.Println("Endgültiger Zählerstand:", counter)}


Hier wartet sync.WaitGroup auf den Abschluss aller Goroutinen, und der Mutex verhindert einen Datenwettlauf (data race). Ohne den Mutex wäre das Ergebnis unvorhersehbar.



4. Concurrency Patterns

Blogs

Buchempfehlungen