Suche…


Einführung

Ein Kanal enthält Werte eines bestimmten Typs. Werte können in einen Kanal geschrieben und von diesem gelesen werden, und sie zirkulieren innerhalb des Kanals in der Reihenfolge des ersten Durchlaufs. Es wird unterschieden zwischen gepufferten Kanälen, die mehrere Meldungen enthalten können, und ungepufferten Kanälen, die dies nicht können. Kanäle werden normalerweise zur Kommunikation zwischen Goroutinen verwendet, sind aber auch in anderen Situationen nützlich.

Syntax

  • make (chan int) // einen ungepufferten Kanal erstellen
  • make (chan int, 5) // einen gepufferten Kanal mit einer Kapazität von 5 erstellen
  • close (ch) // schließt einen Kanal "ch"
  • ch <- 1 // Schreibe den Wert von 1 in einen Kanal "ch"
  • val: = <-ch // lese einen Wert aus Kanal "ch"
  • val, ok: = <-ch // alternative Syntax; ok ist ein Bool, der anzeigt, ob der Kanal geschlossen ist

Bemerkungen

Ein Kanal, der die leere struct make(chan struct{}) ist eine klare Nachricht an den Benutzer, dass keine Informationen über den Kanal übertragen werden und dass diese ausschließlich zur Synchronisation verwendet werden.

Bei ungepufferten Kanälen wird ein Kanalschreiben blockiert, bis ein entsprechender Lesevorgang von einer anderen Goroutine erfolgt. Dasselbe gilt für das Blockieren eines Kanals, während auf einen Brenner gewartet wird.

Bereich verwenden

Beim Lesen mehrerer Werte aus einem Kanal ist die Verwendung eines range ein übliches Muster:

func foo() chan int {
    ch := make(chan int)
    
    go func() {
        ch <- 1
        ch <- 2
        ch <- 3
        close(ch)

    }()
    
    return ch
}

func main() {    
    for n := range foo() {
        fmt.Println(n)
    }
    
    fmt.Println("channel is now closed")
}

Spielplatz

Ausgabe

1
2
3
channel is now closed

Timeouts

Kanäle werden häufig zur Implementierung von Timeouts verwendet.

func main() {
    // Create a buffered channel to prevent a goroutine leak. The buffer
    // ensures that the goroutine below can eventually terminate, even if
    // the timeout is met. Without the buffer, the send on the channel
    // blocks forever, waiting for a read that will never happen, and the
    // goroutine is leaked.
    ch := make(chan struct{}, 1)

    go func() {
        time.Sleep(10 * time.Second)
        ch <- struct{}{}
    }()

    select {
    case <-ch:
        // Work completed before timeout.
    case <-time.After(1 * time.Second):
        // Work was not completed after 1 second.
    }
}

Koordinierende Goroutinen

Stellen Sie sich eine Goroutine mit einem zweistufigen Prozess vor, bei der der Haupt-Thread zwischen jedem Schritt etwas Arbeit ausführen muss:

func main() {
    ch := make(chan struct{})
    go func() {
        // Wait for main thread's signal to begin step one
        <-ch
        
        // Perform work
        time.Sleep(1 * time.Second)
        
        // Signal to main thread that step one has completed
        ch <- struct{}{}
        
        // Wait for main thread's signal to begin step two
        <-ch
        
        // Perform work
        time.Sleep(1 * time.Second)
        
        // Signal to main thread that work has completed
        ch <- struct{}{}
    }()
    
    // Notify goroutine that step one can begin
    ch <- struct{}{}
    
    // Wait for notification from goroutine that step one has completed
    <-ch

    // Perform some work before we notify
    // the goroutine that step two can begin
    time.Sleep(1 * time.Second)
    
    // Notify goroutine that step two can begin
    ch <- struct{}{}
    
    // Wait for notification from goroutine that step two has completed
    <-ch
}

Gepuffert vs ungepuffert

func bufferedUnbufferedExample(buffered bool) {
    // We'll declare the channel, and we'll make it buffered or
    // unbuffered depending on the parameter `buffered` passed
    // to this function.
    var ch chan int
    if buffered {
        ch = make(chan int, 3)
    } else {
        ch = make(chan int)
    }
    
    // We'll start a goroutine, which will emulate a webserver
    // receiving tasks to do every 25ms.
    go func() {
        for i := 0; i < 7; i++ {
            // If the channel is buffered, then while there's an empty
            // "slot" in the channel, sending to it will not be a
            // blocking operation. If the channel is full, however, we'll
            // have to wait until a "slot" frees up.
            // If the channel is unbuffered, sending will block until
            // there's a receiver ready to take the value. This is great
            // for goroutine synchronization, not so much for queueing
            // tasks for instance in a webserver, as the request will
            // hang until the worker is ready to take our task.
            fmt.Println(">", "Sending", i, "...")
            ch <- i
            fmt.Println(">", i, "sent!")
            time.Sleep(25 * time.Millisecond)
        }
        // We'll close the channel, so that the range over channel
        // below can terminate.
        close(ch)
    }()
    
    for i := range ch {
        // For each task sent on the channel, we would perform some
        // task. In this case, we will assume the job is to
        // "sleep 100ms".
        fmt.Println("<", i, "received, performing 100ms job")
        time.Sleep(100 * time.Millisecond)
        fmt.Println("<", i, "job done")
    }
}

Spielplatz gehen

Sperren und Entsperren von Kanälen

Standardmäßig erfolgt die Kommunikation über die Kanäle synchron. Wenn Sie einen Wert senden, muss es einen Empfänger geben. Andernfalls erhalten Sie einen fatal error: all goroutines are asleep - deadlock! wie folgt:

package main

import "fmt"

func main() {
    msg := make(chan string)
    msg <- "Hey There"
    go func() {
        fmt.Println(<-msg)
    }()
}

Es gibt jedoch eine Lösungsverwendung: Verwenden Sie gepufferte Kanäle:

package main

import "fmt"
import "time"

func main() {
    msg :=make(chan string, 1)
    msg <- "Hey There!"
    go func() {
        fmt.Println(<-msg)
    }()
    time.Sleep(time.Second * 1)
}

Warten, bis die Arbeit abgeschlossen ist

Eine gebräuchliche Technik für die Verwendung von Kanälen besteht darin, eine Anzahl von Arbeitern (oder Verbrauchern) zum Lesen aus dem Kanal zu erstellen. Die Verwendung einer sync.WaitGroup ist eine einfache Möglichkeit, auf die Ausführung dieser Worker zu warten.

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    numPiecesOfWork := 20
    numWorkers := 5

    workCh := make(chan int)
    wg := &sync.WaitGroup{}

    // Start workers
    wg.Add(numWorkers)
    for i := 0; i < numWorkers; i++ {
        go worker(workCh, wg)
    }

    // Send work
    for i := 0; i < numPiecesOfWork; i++ {
        work := i % 10 // invent some work
        workCh <- work
    }

    // Tell workers that no more work is coming
    close(workCh)

    // Wait for workers to finish
    wg.Wait()

    fmt.Println("done")
}

func worker(workCh <-chan int, wg *sync.WaitGroup) {
    defer wg.Done() // will call wg.Done() right before returning

    for work := range workCh { // will wait for work until workCh is closed
        doWork(work)
    }
}

func doWork(work int) {
    time.Sleep(time.Duration(work) * time.Millisecond)
    fmt.Println("slept for", work, "milliseconds")
}


Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow