8 Concurrency

Concurrency ermöglicht es uns, mehrere Aufgaben gleichzeitig auszuführen, was zu effizienterem und schnellerem Code führt.

8.1 Goroutines

In Go sind Goroutines die grundlegenden Bausteine für Concurrency. Eine Goroutine ist eine leichtgewichtige Ausführungseinheit, die parallel zu anderen Goroutines läuft. Goroutines sind viel leichter als traditionelle Threads und ermöglichen es, Tausende von ihnen gleichzeitig auszuführen.

8.1.1 Starten einer Goroutine

Um eine Goroutine zu starten, verwenden wir das Schlüsselwort go gefolgt von einem Funktionsaufruf. Hier ein einfaches Beispiel:

package main

import (
    "fmt"
    "time"
)

func sayHello() {
    fmt.Println("Hello, Go!")
}

func main() {
    go sayHello()
    time.Sleep(1 * time.Second)
}

In diesem Beispiel:

  1. Wir definieren eine Funktion sayHello, die “Hello, Go!” auf dem Bildschirm ausgibt.
  2. Im main-Paket starten wir eine Goroutine mit go sayHello(). Dies bedeutet, dass die Funktion sayHello parallel zur main-Funktion ausgeführt wird.
  3. Wir verwenden time.Sleep(1 * time.Second), um das Hauptprogramm für eine Sekunde anzuhalten und sicherzustellen, dass die Goroutine Zeit hat, ihre Arbeit zu erledigen, bevor das Programm endet.

8.2 Channels

Channels sind Kommunikationsmechanismen, die es Goroutines ermöglichen, miteinander zu kommunizieren und Daten sicher auszutauschen. Ein Channel kann als eine Art Pipeline betrachtet werden, durch die Daten von einer Goroutine zu einer anderen fließen.

8.2.1 Deklaration und Nutzung von Channels

Ein Channel wird mit dem make-Befehl erstellt, gefolgt vom Typ der Daten, die durch den Channel fließen sollen. Hier ein Beispiel:

package main

import "fmt"

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

    go func() {
        messages <- "Hello, Channel!"
    }()

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

In diesem Beispiel:

  1. Wir erstellen einen Channel namens messages, der Zeichenketten (string) überträgt.
  2. Wir starten eine anonyme Goroutine, die die Nachricht “Hello, Channel!” in den Channel messages sendet, indem sie den Ausdruck messages <- "Hello, Channel!" verwendet.
  3. Im Hauptprogramm empfangen wir die Nachricht aus dem Channel messages mit dem Ausdruck msg := <-messages und drucken die Nachricht aus.

8.2.2 Buffered Channels

Channels können auch gepuffert sein, was bedeutet, dass sie eine begrenzte Anzahl von Werten speichern können, bevor sie blockieren. Ein gepufferter Channel wird mit einer zusätzlichen Kapazitätsangabe erstellt:

messages := make(chan string, 2)

In diesem Beispiel haben wir einen gepufferten Channel mit einer Kapazität von zwei erstellt. Wir können bis zu zwei Nachrichten senden, ohne dass eine empfangende Goroutine erforderlich ist.

8.2.3 Verwendung von Buffered Channels

package main

import "fmt"

func main() {
    messages := make(chan string, 2)

    messages <- "Message One"
    messages <- "Message Two"

    fmt.Println(<-messages)
    fmt.Println(<-messages)
}

In diesem Beispiel senden wir zwei Nachrichten in den Channel messages, der eine Kapazität von zwei hat. Dann empfangen wir die Nachrichten und drucken sie aus.

8.3 Select

Das select-Statement in Go ermöglicht es einer Goroutine, auf mehrere Kommunikationsoperationen gleichzeitig zu warten. Es ist ähnlich wie ein switch, aber für Channels.

8.3.1 Verwendung von Select

Hier ist ein einfaches Beispiel, um zu zeigen, wie select funktioniert:

package main

import (
    "fmt"
    "time"
)

func main() {
    c1 := make(chan string)
    c2 := make(chan string)

    go func() {
        time.Sleep(1 * time.Second)
        c1 <- "one"
    }()
    go func() {
        time.Sleep(2 * time.Second)
        c2 <- "two"
    }()

    for i := 0; i < 2; i++ {
        select {
        case msg1 := <-c1:
            fmt.Println("received", msg1)
        case msg2 := <-c2:
            fmt.Println("received", msg2)
        }
    }
}

In diesem Beispiel:

  1. Wir erstellen zwei Channels, c1 und c2.
  2. Wir starten zwei Goroutines, die jeweils eine Nachricht nach einer bestimmten Zeit in die Channels senden.
  3. Im Hauptprogramm verwenden wir eine Schleife und ein select-Statement, um auf Nachrichten von beiden Channels zu warten. Wenn eine Nachricht empfangen wird, drucken wir sie aus.

Das select-Statement wartet, bis eine der Cases ausführbar ist, und führt dann diese aus. Wenn mehrere Cases ausführbar sind, wird eine zufällig ausgewählt.