Zoeken…


Invoering

Een kanaal bevat waarden van een bepaald type. Waarden kunnen naar een kanaal worden geschreven en ervan worden gelezen, en ze circuleren in het kanaal in de volgorde van de eerste in de eerste uit. Er is een onderscheid tussen gebufferde kanalen die verschillende berichten kunnen bevatten en niet-gebufferde kanalen die dat niet kunnen. Kanalen worden meestal gebruikt om te communiceren tussen goroutines, maar zijn ook nuttig in andere omstandigheden.

Syntaxis

  • make (chan int) // maak een ongebufferd kanaal
  • make (chan int, 5) // maak een gebufferd kanaal met een capaciteit van 5
  • close (ch) // sluit een kanaal "ch"
  • ch <- 1 // schrijf de waarde van 1 naar een kanaal "ch"
  • val: = <-ch // lees een waarde van kanaal "ch"
  • val, ok: = <-ch // alternatieve syntaxis; ok is een bool die aangeeft of het kanaal is gesloten

Opmerkingen

Een kanaal met het lege struct make(chan struct{}) is een duidelijk bericht aan de gebruiker dat er geen informatie over het kanaal wordt verzonden en dat het puur voor synchronisatie wordt gebruikt.

Met betrekking tot niet-gebufferde kanalen, zal een kanaalschrijven blokkeren totdat een overeenkomstige uitlezing van een andere goroutine plaatsvindt. Hetzelfde geldt voor het blokkeren van het lezen van kanalen tijdens het wachten op een schrijver.

Gebruik bereik

Bij het lezen van meerdere waarden van een kanaal is het gebruik van range een gebruikelijk patroon:

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")
}

Speelplaats

uitgang

1
2
3
channel is now closed

Time-outs

Kanalen worden vaak gebruikt om time-outs te implementeren.

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.
    }
}

Coördinerende goroutines

Stel je een goroutine voor met een tweestapsproces, waarbij de hoofdthread tussen elke stap wat werk moet doen:

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
}

Gebufferd versus ongebufferd

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")
    }
}

speelplaats

Zenders blokkeren en deblokkeren

Communicatie via de kanalen is standaard gesynchroniseerd; als je wat waarde verstuurt, moet er een ontvanger zijn. Anders krijgt u een fatal error: all goroutines are asleep - deadlock! als volgt:

package main

import "fmt"

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

Maar er is een oplossing: gebruik gebufferde kanalen:

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)
}

Wachten tot het werk klaar is

Een veel voorkomende techniek voor het gebruik van kanalen is om een aantal werknemers (of consumenten) te maken om van het kanaal te lezen. Met behulp van een sync.WaitGroup is een eenvoudige manier om te wachten tot die werknemers klaar zijn met rennen.

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
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow