Parallelität und Multithreading in Go: Fortgeschrittenes Niveau

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

Parallelität und Multithreading in Go: Fortgeschrittenes Niveau



Go (Golang) wurde von Anfang an als Sprache für die Entwicklung skalierbarer und leistungsfähiger Anwendungen konzipiert. Grundlage dafür ist ein leistungsstarkes Parallelitätsmodell, das auf Goroutinen und Kanälen basiert. Wenn Sie bereits die grundlegenden Beispiele mit go func() und einfachen Kanälen gemeistert haben, ist es an der Zeit, zur nächsten Stufe überzugehen. In diesem Artikel werden wir fortgeschrittene Muster für die Arbeit mit Parallelität in Go behandeln, die Ihnen helfen, zuverlässigen, effizienten und sicheren Code zu schreiben.



1. Verwaltung des Lebenszyklus von Goroutinen: Context und WaitGroup



Eines der häufigsten Probleme ist das Auslaufen von Goroutinen. Eine Goroutine, die unendlich auf Daten aus einem Kanal wartet, wird niemals beendet, wenn der Kanal nicht richtig geschlossen wird. Für die Verwaltung des Lebenszyklus von Goroutinen gibt es in Go zwei Hauptwerkzeuge: sync.WaitGroup und context.Context.



sync.WaitGroup zum Warten auf Beendigung

Verwenden Sie sync.WaitGroup, wenn Sie auf die Beendigung einer festgelegten Gruppe von Goroutinen warten müssen. Dies ist ein einfacher und effektiver Mechanismus.



package main

import (\t"fmt"\t"sync"\t"time")

func worker(id int, wg *sync.WaitGroup) {\tdefer wg.Done() // Zähler beim Verlassen verringern\tfmt.Printf("Worker %d hat die Arbeit begonnen", id)\ttime.Sleep(time.Second)\tfmt.Printf("Worker %d hat die Arbeit beendet", id)}

func main() {\tvar wg sync.WaitGroup

\tfor i := 1; i <= 3; i++ {\t\twg.Add(1) // Zähler vor dem Start erhöhen\t\tgo worker(i, &wg)\t}

\twg.Wait() // Blockieren, bis der Zähler 0 ist\tfmt.Println("Alle Worker wurden beendet")}


context.Context für Abbruch und Fristen

Wenn eine Goroutine in einer Schleife läuft oder eine langwierige Operation ausführt, muss sie in der Lage sein, sich auf ein externes Signal hin korrekt zu beenden. context.Context ist der Standardweg, um ein Signal für Abbruch, Frist oder Timeout zu übermitteln.



package main

import (\t"context"\t"fmt"\t"time")

func longRunningTask(ctx context.Context) {\tfor {\t\tselect {\t\tcase <-ctx.Done():\t\t\tfmt.Println("Goroutine hat Stoppsignal erhalten")\t\t\treturn\t\tdefault:\t\t\ttime.Sleep(500 * time.Millisecond)\t\t\tfmt.Println("Arbeite...")\t\t}\t}}

func main() {\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\tdefer cancel() // Wichtig: cancel aufrufen, um Ressourcen freizugeben

\tgo longRunningTask(ctx)

\ttime.Sleep(3 * time.Second)\tfmt.Println("Main wurde beendet")}


2. Kanäle: Pufferung, Schließen und das Fan-Out/Fan-In-Muster



Kanäle sind die primäre Methode zur Synchronisation und Datenübertragung zwischen Goroutinen. Auf fortgeschrittenem Niveau ist es wichtig, den Unterschied zwischen gepufferten und ungepufferten Kanälen zu verstehen, sie richtig zu schließen und Aggregationsmuster zu verwenden.



Gepuffert vs. Ungepuffert

Ein ungepufferter Kanal (ohne Puffer) blockiert den Sender, bis der Empfänger die Daten gelesen hat. Ein gepufferter Kanal ermöglicht das Senden mehrerer Nachrichten ohne Wartezeit, bis der Puffer voll ist. Die Wahl hängt von der Aufgabe ab: Für strenge Synchronisation verwenden Sie ungepufferte, zum Glätten der Last gepufferte Kanäle.



Fan-Out/Fan-In-Muster

Dies ist eines der leistungsstärksten Muster. Fan-Out – Verteilung von Aufgaben auf mehrere Goroutinen (Worker). Fan-In – Sammeln von Ergebnissen mehrerer Goroutinen in einem Kanal.



package main

import (\t"fmt"\t"sync")

func producer(nums ...int) <-chan int {\tout := make(chan int)\tgo func() {\t\tfor _, n := range nums {\t\t\tout <- n\t\t}\t\tclose(out)\t}()\treturn out}

func square(in <-chan int) <-chan int {\tout := make(chan int)\tgo func() {\t\tfor n := range in {\t\t\tout <- n * n\t\t}\t\tclose(out)\t}()\treturn out}

func merge(channels ...<-chan int) <-chan int {\t

Blogs

Buchempfehlungen