Heim > Backend-Entwicklung > Golang > Goroutinen und Kanäle in Golang mit intuitiven Bildern verstehen

Goroutinen und Kanäle in Golang mit intuitiven Bildern verstehen

Mary-Kate Olsen
Freigeben: 2024-12-30 16:18:09
Original
782 Leute haben es durchsucht

⚠️ Wie geht man bei dieser Serie vor?

1. Führen Sie jedes Beispiel aus: Lesen Sie nicht nur den Code. Geben Sie es ein, führen Sie es aus und beobachten Sie das Verhalten.
2. Experimentieren Sie und machen Sie Dinge kaputt: Entfernen Sie Schlafphasen und sehen Sie, was passiert, ändern Sie die Kanalpuffergrößen, ändern Sie die Anzahl der Goroutinen.
Wenn man Dinge kaputt macht, lernt man, wie sie funktionieren
3. Grund für das Verhalten: Bevor Sie geänderten Code ausführen, versuchen Sie, das Ergebnis vorherzusagen. Wenn Sie unerwartetes Verhalten bemerken, halten Sie inne und überlegen Sie, warum. Stellen Sie die Erklärungen in Frage.
4. Erstellen Sie mentale Modelle: Jede Visualisierung repräsentiert ein Konzept. Versuchen Sie, Ihre eigenen Diagramme für geänderten Code zu zeichnen.

Understanding Goroutines and Channels in Golang with Intuitive Visuals

Dies ist Teil 1 unserer Serie „Mastering Go Concurrency“, in der wir Folgendes behandeln:

  • Wie Goroutinen funktionieren und ihr Lebenszyklus
  • Kanalkommunikation zwischen Goroutinen
  • Gepufferte Kanäle und ihre Anwendungsfälle
  • Praxisbeispiele und Visualisierungen

Wir beginnen mit den Grundlagen und entwickeln schrittweise ein Gespür dafür, wie wir diese effektiv nutzen können.

Es wird etwas lang, eher sehr lang, also macht euch bereit.

Understanding Goroutines and Channels in Golang with Intuitive Visuals

Wir stehen Ihnen während des gesamten Prozesses zur Seite.

Grundlagen von Goroutinen

Beginnen wir mit einem einfachen Programm, das mehrere Dateien herunterlädt.

package main

import (
    "fmt"
    "time"
)

func downloadFile(filename string) {
    fmt.Printf("Starting download: %s\n", filename)
    // Simulate file download with sleep
    time.Sleep(2 * time.Second)
    fmt.Printf("Finished download: %s\n", filename)
}

func main() {
    fmt.Println("Starting downloads...")

    startTime := time.Now()

    downloadFile("file1.txt")
    downloadFile("file2.txt")
    downloadFile("file3.txt")

    elapsedTime := time.Since(startTime)

    fmt.Printf("All downloads completed! Time elapsed: %s\n", elapsedTime)
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Understanding Goroutines and Channels in Golang with Intuitive Visuals

Das Programm dauert insgesamt 6 Sekunden, da jeder 2-sekündige Download abgeschlossen sein muss, bevor der nächste beginnt. Stellen wir uns das vor:

Understanding Goroutines and Channels in Golang with Intuitive Visuals

Wir können diese Zeit verkürzen, indem wir unser Programm so ändern, dass es Go-Routinen verwendet:

Hinweis: Schlüsselwort vor Funktionsaufruf eingeben

package main

import (
    "fmt"
    "time"
)

func downloadFile(filename string) {
    fmt.Printf("Starting download: %s\n", filename)
    // Simulate file download with sleep
    time.Sleep(2 * time.Second)
    fmt.Printf("Finished download: %s\n", filename)
}

func main() {
    fmt.Println("Starting downloads...")

    // Launch downloads concurrently
    go downloadFile("file1.txt")
    go downloadFile("file2.txt")
    go downloadFile("file3.txt")

    fmt.Println("All downloads completed!")
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Warte was? wurde nichts gedruckt? Warum?

Understanding Goroutines and Channels in Golang with Intuitive Visuals

Lassen Sie uns dies visualisieren, um zu verstehen, was passieren könnte.

Understanding Goroutines and Channels in Golang with Intuitive Visuals

Aus der obigen Visualisierung gehen wir hervor, dass die Hauptfunktion existiert, bevor die Goroutinen fertig sind. Eine Beobachtung ist, dass der gesamte Lebenszyklus einer Goroutine von der Hauptfunktion abhängt.

Hinweis: Die Hauptfunktion an sich ist eine Goroutine ;)

Um dies zu beheben, müssen wir die Haupt-Goroutine warten lassen, bis die anderen Goroutinen abgeschlossen sind. Dafür gibt es mehrere Möglichkeiten:

  1. Warten Sie ein paar Sekunden (knifflige Art)
  2. WaitGroup verwenden (richtige Vorgehensweise, als nächstes)
  3. Verwendung von Kanälen (wir werden weiter unten darauf eingehen)

Lassen Sie uns einige Sekunden warten, bis die Go-Routinen abgeschlossen sind.

package main

import (
    "fmt"
    "time"
)

func downloadFile(filename string) {
    fmt.Printf("Starting download: %s\n", filename)
    // Simulate file download with sleep
    time.Sleep(2 * time.Second)
    fmt.Printf("Finished download: %s\n", filename)
}

func main() {
    fmt.Println("Starting downloads...")

    startTime := time.Now()

    downloadFile("file1.txt")
    downloadFile("file2.txt")
    downloadFile("file3.txt")

    elapsedTime := time.Since(startTime)

    fmt.Printf("All downloads completed! Time elapsed: %s\n", elapsedTime)
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Das Problem dabei ist, dass wir möglicherweise nicht wissen, wie viel Zeit eine Goroutine dauern könnte. In diesem Fall haben wir jeweils eine konstante Zeit, aber in realen Szenarien sind wir uns bewusst, dass die Downloadzeit variiert.

Kommt die sync.WaitGroup

Eine sync.WaitGroup in Go ist ein Parallelitätskontrollmechanismus, der verwendet wird, um darauf zu warten, dass die Ausführung einer Sammlung von Goroutinen abgeschlossen ist.

Hier sehen wir uns das in Aktion an und visualisieren es:

package main

import (
    "fmt"
    "time"
)

func downloadFile(filename string) {
    fmt.Printf("Starting download: %s\n", filename)
    // Simulate file download with sleep
    time.Sleep(2 * time.Second)
    fmt.Printf("Finished download: %s\n", filename)
}

func main() {
    fmt.Println("Starting downloads...")

    // Launch downloads concurrently
    go downloadFile("file1.txt")
    go downloadFile("file2.txt")
    go downloadFile("file3.txt")

    fmt.Println("All downloads completed!")
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Understanding Goroutines and Channels in Golang with Intuitive Visuals

Lassen Sie uns dies visualisieren und die Funktionsweise von sync.WaitGroup verstehen:

Understanding Goroutines and Channels in Golang with Intuitive Visuals

Gegenmechanismus:

  • WaitGroup unterhält einen internen Zähler
  • wg.Add(n) erhöht den Zähler um n
  • wg.Done() dekrementiert den Zähler um 1
  • wg.Wait() blockiert, bis der Zähler 0 erreicht

Synchronisierungsablauf:

  • Haupt-Goroutine ruft Add(3) auf, bevor Goroutinen gestartet werden
  • Jede Goroutine ruft Done() auf, wenn sie abgeschlossen ist
  • Die Hauptgoroutine wird bei Wait() blockiert, bis der Zähler 0 erreicht
  • Wenn der Zähler 0 erreicht, wird das Programm fortgesetzt und sauber beendet

Häufige Fallstricke, die es zu vermeiden gilt
package main

import (
    "fmt"
    "time"
)

func downloadFile(filename string) {
    fmt.Printf("Starting download: %s\n", filename)
    // Simulate file download with sleep
    time.Sleep(2 * time.Second)
    fmt.Printf("Finished download: %s\n", filename)
}

func main() {
    fmt.Println("Starting downloads...")

    startTime := time.Now() // Record start time

    go downloadFile("file1.txt")
    go downloadFile("file2.txt")
    go downloadFile("file3.txt")

    // Wait for goroutines to finish
    time.Sleep(3 * time.Second)

    elapsedTime := time.Since(startTime)

    fmt.Printf("All downloads completed! Time elapsed: %s\n", elapsedTime)
}
Nach dem Login kopieren
Nach dem Login kopieren

Kanäle

So haben wir ein gutes Verständnis dafür bekommen, wie die Goroutinen funktionieren. Nein, wie kommunizieren zwei Go-Routinen? Hier kommt der Kanal ins Spiel.

Kanäle in Go sind ein leistungsstarkes Nebenläufigkeitsprimitiv, das für die Kommunikation zwischen Goroutinen verwendet wird. Sie bieten Goroutinen die Möglichkeit, Daten sicher auszutauschen.

Stellen Sie sich Kanäle als Pipes vor: Eine Goroutine kann Daten an einen Kanal senden und eine andere kann sie empfangen.

hier sind einige Eigenschaften:

  1. Kanäle blockieren von Natur aus.
  2. Eine An Kanal senden Operation ch <- value blockiert, bis eine andere Goroutine vom Kanal empfängt.
  3. Eine Vom Kanal empfangen-Operation <-ch blockiert, bis eine andere Goroutine an den Kanal sendet.
package main

import (
    "fmt"
    "time"
)

func downloadFile(filename string) {
    fmt.Printf("Starting download: %s\n", filename)
    // Simulate file download with sleep
    time.Sleep(2 * time.Second)
    fmt.Printf("Finished download: %s\n", filename)
}

func main() {
    fmt.Println("Starting downloads...")

    startTime := time.Now()

    downloadFile("file1.txt")
    downloadFile("file2.txt")
    downloadFile("file3.txt")

    elapsedTime := time.Since(startTime)

    fmt.Printf("All downloads completed! Time elapsed: %s\n", elapsedTime)
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Understanding Goroutines and Channels in Golang with Intuitive Visuals

Warum führt ch <- „hello“ zu einem Deadlock? Da Kanäle von Natur aus blockieren und wir hier „Hallo“ weitergeben, wird die Haupt-Goroutine blockiert, bis es einen Empfänger gibt, und da es keinen Empfänger gibt, bleibt sie hängen.

Understanding Goroutines and Channels in Golang with Intuitive Visuals

Beheben wir dieses Problem, indem wir eine Goroutine hinzufügen

package main

import (
    "fmt"
    "time"
)

func downloadFile(filename string) {
    fmt.Printf("Starting download: %s\n", filename)
    // Simulate file download with sleep
    time.Sleep(2 * time.Second)
    fmt.Printf("Finished download: %s\n", filename)
}

func main() {
    fmt.Println("Starting downloads...")

    // Launch downloads concurrently
    go downloadFile("file1.txt")
    go downloadFile("file2.txt")
    go downloadFile("file3.txt")

    fmt.Println("All downloads completed!")
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Lass uns das visualisieren:

Understanding Goroutines and Channels in Golang with Intuitive Visuals

Diesmal wird die Nachricht von einer anderen Goroutine gesendet, damit die Haupt-Goroutine nicht blockiert wird, während sie an den Kanal gesendet wird, sodass sie zu msg := <-ch verschoben wird, wo sie die Haupt-Goroutine blockiert, bis sie die empfängt Nachricht.

Behebung des Hauptproblems, bei der Verwendung des Kanals nicht auf andere zu warten

Jetzt verwenden wir den Kanal, um das Problem mit dem Datei-Downloader zu beheben (main wartet nicht, bis andere fertig sind).

package main

import (
    "fmt"
    "time"
)

func downloadFile(filename string) {
    fmt.Printf("Starting download: %s\n", filename)
    // Simulate file download with sleep
    time.Sleep(2 * time.Second)
    fmt.Printf("Finished download: %s\n", filename)
}

func main() {
    fmt.Println("Starting downloads...")

    startTime := time.Now() // Record start time

    go downloadFile("file1.txt")
    go downloadFile("file2.txt")
    go downloadFile("file3.txt")

    // Wait for goroutines to finish
    time.Sleep(3 * time.Second)

    elapsedTime := time.Since(startTime)

    fmt.Printf("All downloads completed! Time elapsed: %s\n", elapsedTime)
}
Nach dem Login kopieren
Nach dem Login kopieren

Understanding Goroutines and Channels in Golang with Intuitive Visuals

Visualisierung:

Understanding Goroutines and Channels in Golang with Intuitive Visuals

Machen wir einen Probelauf, um es besser zu verstehen:

Programmstart:

Hauptgoroutine erstellt Fertigkanal
Startet drei Download-Goroutinen
Jede Goroutine erhält einen Verweis auf denselben Kanal

Ausführung herunterladen:

  1. Alle drei Downloads laufen gleichzeitig
  2. Jeder dauert 2 Sekunden
  3. Sie können in beliebiger Reihenfolge enden

Kanalschleife:

  1. Hauptgoroutine tritt in die Schleife ein: for i := 0; ich < 3; ich
  2. Jedes <-done blockiert, bis ein Wert empfangen wird
  3. Die Schleife stellt sicher, dass wir auf alle drei Abschlusssignale warten

Understanding Goroutines and Channels in Golang with Intuitive Visuals

Schleifenverhalten:

  1. Iteration 1: Blockiert, bis der erste Download abgeschlossen ist
  2. Iteration 2: Blockiert, bis der zweite Download abgeschlossen ist
  3. Iteration 3: Blockiert, bis der endgültige Download abgeschlossen ist

Die Reihenfolge der Fertigstellung spielt keine Rolle!

Beobachtungen:
⭐ Jeder Versand (erledigt <- true) hat genau einen Empfang (<-erledigt)
⭐ Die Hauptgoroutine koordiniert alles durch die Schleife

Wie können zwei Goroutinen kommunizieren?

Wir haben bereits gesehen, wie zwei Goroutinen kommunizieren können. Wann? Die ganze Zeit. Vergessen wir nicht, dass die Hauptfunktion auch eine Goroutine ist.

package main

import (
    "fmt"
    "time"
)

func downloadFile(filename string) {
    fmt.Printf("Starting download: %s\n", filename)
    // Simulate file download with sleep
    time.Sleep(2 * time.Second)
    fmt.Printf("Finished download: %s\n", filename)
}

func main() {
    fmt.Println("Starting downloads...")

    startTime := time.Now()

    downloadFile("file1.txt")
    downloadFile("file2.txt")
    downloadFile("file3.txt")

    elapsedTime := time.Since(startTime)

    fmt.Printf("All downloads completed! Time elapsed: %s\n", elapsedTime)
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Lassen Sie uns dies visualisieren und einen Probelauf durchführen:

Understanding Goroutines and Channels in Golang with Intuitive Visuals

Probelauf:

Programmstart (t=0ms)

  • Die Haupt-Goroutine initialisiert drei Kanäle:
    • ch: für Nachrichten.
    • senderDone: um den Abschluss des Absenders zu signalisieren.
    • ReceiverDone: um den Abschluss des Empfängers zu signalisieren.
  • Die Haupt-Goroutine startet zwei Goroutinen:
    • Absender.
    • Empfänger.
  • Die Haupt-Goroutine-Blöcke warten auf ein Signal von <-senderDone.

Erste Nachricht (t=1ms)

  1. Der Absender sendet „Nachricht 1“ an den CH-Kanal.
  2. Der Empfänger wacht auf und verarbeitet die Nachricht:
    • Druckt: „Empfangen: Nachricht 1“.
  3. Der Absender schläft 100 ms lang.

Zweite Nachricht (t=101ms)

  1. Der Absender wacht auf und sendet „Nachricht 2“ an den CH-Kanal.
  2. Der Empfänger verarbeitet die Nachricht:
    • Druckt: „Empfangen: Nachricht 2“.
  3. Der Absender schläft weitere 100 ms.

Dritte Nachricht (t=201ms)

  1. Der Absender wacht auf und sendet „Nachricht 3“ an den CH-Kanal.
  2. Der Empfänger verarbeitet die Nachricht:
    • Druckt: „Empfangen: Nachricht 3“.
  3. Der Absender schläft zum letzten Mal.

Kanal schließen (t=301 ms)

  1. Der Absender schläft nicht mehr und schließt den CH-Kanal.
  2. Der Absender sendet ein True-Signal an den SenderDone-Kanal, um den Abschluss anzuzeigen.
  3. Der Empfänger erkennt, dass der Kanal geschlossen wurde.
  4. Der Empfänger verlässt seine For-Range-Schleife.

Abschluss (t=302–303 ms)

  1. Die Hauptgoroutine empfängt das Signal von senderDone und hört auf zu warten.
  2. Die Hauptgoroutine beginnt, auf ein Signal vom Empfänger zu warten. Fertig.
  3. Der Empfänger sendet ein Abschlusssignal an den EmpfängerDone-Kanal.
  4. Die Hauptgoroutine empfängt das Signal und gibt Folgendes aus:
    • „Alle Vorgänge abgeschlossen!“.
  5. Das Programm wird beendet.

Gepufferte Kanäle

Warum brauchen wir gepufferte Kanäle?
Ungepufferte Kanäle blockieren sowohl den Sender als auch den Empfänger, bis die andere Seite bereit ist. Wenn Hochfrequenzkommunikation erforderlich ist, können ungepufferte Kanäle zu einem Engpass werden, da beide Goroutinen pausieren müssen, um Daten auszutauschen.

Eigenschaften gepufferter Kanäle:

  1. FIFO (First In, First Out, ähnlich der Warteschlange)
  2. Feste Größe, bei der Erstellung festgelegt
  3. Blockiert den Absender, wenn der Puffer voll ist
  4. Blockiert den Empfänger, wenn der Puffer leer ist

Understanding Goroutines and Channels in Golang with Intuitive Visuals

Wir sehen es in Aktion:

package main

import (
    "fmt"
    "time"
)

func downloadFile(filename string) {
    fmt.Printf("Starting download: %s\n", filename)
    // Simulate file download with sleep
    time.Sleep(2 * time.Second)
    fmt.Printf("Finished download: %s\n", filename)
}

func main() {
    fmt.Println("Starting downloads...")

    startTime := time.Now()

    downloadFile("file1.txt")
    downloadFile("file2.txt")
    downloadFile("file3.txt")

    elapsedTime := time.Since(startTime)

    fmt.Printf("All downloads completed! Time elapsed: %s\n", elapsedTime)
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Ausgabe (vor dem Auskommentieren des ch<-"dritten")

Understanding Goroutines and Channels in Golang with Intuitive Visuals

Warum hat es die Haupt-Goroutine nicht blockiert?

  1. Ein gepufferter Kanal ermöglicht das Senden bis zu seiner Kapazität, ohneden Absender zu blockieren.

  2. Der Kanal hat eine Kapazität von 2, was bedeutet, dass er zwei Werte in seinem Puffer speichern kann, bevor er blockiert wird.

  3. Der Puffer ist bereits mit „erster“ und „zweiter“ voll. Da es keinen gleichzeitigen Empfänger gibt, der diese Werte konsumiert, wird der Sendevorgang auf unbestimmte Zeit blockiert.

  4. Da die Haupt-Goroutine auch für das Senden verantwortlich ist und es keine anderen aktiven Goroutinen gibt, die Werte vom Kanal empfangen, gerät das Programm beim Versuch, die dritte Nachricht zu senden, in einen Deadlock.

Das Auskommentieren der dritten Nachricht führt zu einem Deadlock, da die Kapazität jetzt voll ist und die dritte Nachricht blockiert wird, bis der Puffer frei wird.

Understanding Goroutines and Channels in Golang with Intuitive Visuals

Wann sollten gepufferte Kanäle im Vergleich zu ungepufferten Kanälen verwendet werden?

Aspect Buffered Channels Unbuffered Channels
Purpose For decoupling sender and receiver timing. For immediate synchronization between sender and receiver.
When to Use - When the sender can proceed without waiting for receiver. - When sender and receiver must synchronize directly.
- When buffering improves performance or throughput. - When you want to enforce message-handling immediately.
Blocking Behavior Blocks only when buffer is full. Sender blocks until receiver is ready, and vice versa.
Performance Can improve performance by reducing synchronization. May introduce latency due to synchronization.
Example Use Cases - Logging with rate-limited processing. - Simple signaling between goroutines.
- Batch processing where messages are queued temporarily. - Hand-off of data without delay or buffering.
Complexity Requires careful buffer size tuning to avoid overflows. Simpler to use; no tuning needed.
Overhead Higher memory usage due to the buffer. Lower memory usage; no buffer involved.
Concurrency Pattern Asynchronous communication between sender and receiver. Synchronous communication; tight coupling.
Error-Prone Scenarios Deadlocks if buffer size is mismanaged. Deadlocks if no goroutine is ready to receive or send.

Wichtige Erkenntnisse

Verwenden Sie gepufferte Kanäle, wenn:

  1. Sie müssen das Timing von Sender und Empfänger entkoppeln.
  2. Die Leistung kann durch das Stapeln oder Einreihen von Nachrichten in die Warteschlange verbessert werden.
  3. Die Anwendung kann Verzögerungen bei der Verarbeitung von Nachrichten tolerieren, wenn der Puffer voll ist.

Verwenden Sie ungepufferte Kanäle, wenn:

  1. Die Synchronisierung zwischen Goroutinen ist von entscheidender Bedeutung.
  2. Sie möchten Einfachheit und eine sofortige Übergabe von Daten.
  3. Die Interaktion zwischen Sender und Empfänger muss sofort erfolgen.

Diese Grundlagen schaffen die Grundlage für fortgeschrittenere Konzepte. In unseren kommenden Beiträgen werden wir Folgendes untersuchen:

Nächster Beitrag:

  1. Parallelitätsmuster
  2. Mutex und Speichersynchronisation

Bleiben Sie dran, während wir unser Verständnis der leistungsstarken Parallelitätsfunktionen von Go weiter ausbauen!

Understanding Goroutines and Channels in Golang with Intuitive Visuals

Das obige ist der detaillierte Inhalt vonGoroutinen und Kanäle in Golang mit intuitiven Bildern verstehen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Quelle:dev.to
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Neueste Artikel des Autors
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage