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

\n\n

Parallelität (concurrency) ist eine der Schlüsseleigenschaften der Sprache Go, die auf der Ebene der Laufzeitumgebung und der Syntax in ihre Architektur eingebettet ist. Im Gegensatz zu vielen anderen Sprachen, in denen Multithreading über komplexe Bibliotheken implementiert wird und eine manuelle Thread-Verwaltung erfordert, bietet Go leichte Goroutinen (goroutines) und Kanäle (channels). Auf fortgeschrittenem Niveau geht es nicht nur darum, mehrere Aufgaben zu starten, sondern sie korrekt zu synchronisieren, Datenwettläufe zu vermeiden und Systemressourcen effizient zu nutzen. In diesem Artikel werden wir fortgeschrittene Techniken für die Arbeit mit Parallelität in Go behandeln.

\n\n

1. Goroutinen: Lebenszyklusverwaltung und Prioritäten

\n\n

Goroutinen sind leichtgewichtige Threads, die von der Go-Laufzeitumgebung verwaltet werden. Sie werden mit dem Schlüsselwort go gestartet. Auf fortgeschrittenem Niveau muss man jedoch verstehen, wie man ihre Ausführung und Beendigung steuert.

\n\n

Grundlegender Start einer Goroutine:

\n
package main\n\nimport (\n    "fmt"\n    "time"\n)\n\nfunc worker(id int) {\n    fmt.Printf("Worker %d starting\\n", id)\n    time.Sleep(time.Second)\n    fmt.Printf("Worker %d done\\n", id)\n}\n\nfunc main() {\n    for i := 1; i <= 5; i++ {\n        go worker(i)\n    }\n    time.Sleep(2 * time.Second) // Notlösung: Warten auf Beendigung\n}
\n\n

Problem: In diesem Beispiel verwenden wir time.Sleep zum Warten, was unzuverlässig ist. Auf fortgeschrittenem Niveau werden Kanäle und sync.WaitGroup verwendet.

\n\n

2. Synchronisation mit sync.WaitGroup

\n\n

sync.WaitGroup ermöglicht es, auf die Beendigung einer bestimmten Anzahl von Goroutinen zu warten. Dies ist ein grundlegendes, aber leistungsstarkes Werkzeug zur Synchronisation.

\n\n
package main\n\nimport (\n    "fmt"\n    "sync"\n    "time"\n)\n\nfunc worker(id int, wg *sync.WaitGroup) {\n    defer wg.Done() // Zähler bei Beendigung verringern\n    fmt.Printf("Worker %d starting\\n", id)\n    time.Sleep(time.Second)\n    fmt.Printf("Worker %d done\\n", id)\n}\n\nfunc main() {\n    var wg sync.WaitGroup\n    for i := 1; i <= 5; i++ {\n        wg.Add(1) // Zähler vor dem Start erhöhen\n        go worker(i, &wg)\n    }\n    wg.Wait() // Blockieren, bis der Zähler auf Null ist\n    fmt.Println("All workers completed")\n}
\n\n

Wichtig: wg.Add() muss vor dem Start der Goroutine aufgerufen werden, sonst ist ein Datenwettlauf möglich. Verwenden Sie defer wg.Done() innerhalb der Goroutine, um sicherzustellen, dass der Zähler auch bei einer Panik verringert wird.

\n\n

3. Kanäle: Pufferung, Richtung und select

\n\n

Kanäle sind der primäre Mechanismus für den Datenaustausch zwischen Goroutinen. Auf fortgeschrittenem Niveau werden gepufferte Kanäle, unidirektionale Kanäle und der select-Operator verwendet.

\n\n

3.1. Gepufferte Kanäle

\n

Gepufferte Kanäle ermöglichen das Senden von Daten ohne sofortigen Empfang. Dies reduziert Blockaden und erhöht den Durchsatz.

\n
package main\n\nimport "fmt"\n\nfunc main() {\n    ch := make(chan int, 3) // Kanal mit Puffer für 3 Elemente\n    ch <- 1\n    ch <- 2\n    ch <- 3\n    // ch <- 4 // Blockade, da der Puffer voll ist\n    fmt.Println(<-ch)\n    fmt.Println(<-ch)\n    fmt.Println(<-ch)\n}
\n\n

3.2. Unidirektionale Kanäle

\n

Aus Typsicherheitsgründen und zur expliziten Angabe der Kanalrolle werden unidirektionale Kanäle verwendet: chan<- (nur Senden) und <-chan (nur Empfangen).

\n
package main\n\nimport "fmt"\n\nfunc sender(out chan<- int) {\n    for i := 0; i < 5; i++ {\n        out <- i\n    }\n    close(out)\n}\n\nfunc receiver(in <-chan int) {\n    for val := range in {\n        fmt.Println(val)\n    }\n}\n\nfunc main() {\n    ch := make(chan int)\n    go sender(ch)\n    receiver(ch)\n}
\n\n

3.3. Der select-Operator

\n

select ermöglicht es einer Goroutine, gleichzeitig auf mehrere Kanäle zu warten. Dies ist

Blogs

Buchempfehlungen