Sök…


Introduktion

En kanal innehåller värden av en viss typ. Värden kan skrivas till en kanal och läsas från den, och de cirkulerar inuti kanalen i första-till-första-ut ordning. Det skiljer mellan buffrade kanaler, som kan innehålla flera meddelanden, och obuffrade kanaler, som inte kan. Kanaler används vanligtvis för att kommunicera mellan goroutiner, men är också användbara under andra omständigheter.

Syntax

  • göra (chan int) // skapa en obufferad kanal
  • make (chan int, 5) // skapa en buffrad kanal med en kapacitet på 5
  • stäng (ch) // stänger en kanal "ch"
  • ch <- 1 // skriv värdet 1 till en kanal "ch"
  • val: = <-ch // läs ett värde från kanal "ch"
  • val, ok: = <-ch // alternativ syntax; ok är en bool som anger om kanalen är stängd

Anmärkningar

En kanal som innehåller det tomma struktmärket make(chan struct{}) är ett tydligt meddelande till användaren att ingen information överförs över kanalen och att den rent används för synkronisering.

När det gäller obuffrade kanaler kommer en kanalskrivning att blockeras tills en motsvarande avläsning inträffar från en annan goroutin. Detsamma gäller för en kanal som blockeras medan du väntar på en författare.

Använd räckvidd

När du läser flera värden från en kanal är användning av range ett vanligt mönster:

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

Lekplats

Produktion

1
2
3
channel is now closed

Tidsgränser

Kanaler används ofta för att implementera timeouts.

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

Samordna goroutiner

Föreställ dig en goroutin med en tvåstegsprocess, där huvudtråden måste göra lite arbete mellan varje steg:

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
}

Buffrat kontra obuffrat

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

gå lekplats

Blockering och avblockering av kanaler

Som standard synkroniseras kommunikationen över kanalerna; när du skickar något värde måste det finnas en mottagare. Annars får du ett fatal error: all goroutines are asleep - deadlock! som följer:

package main

import "fmt"

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

Bu det finns en lösning användning: använd buffrade kanaler:

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

Väntar på att arbetet är slut

En vanlig teknik för att använda kanaler är att skapa ett antal arbetare (eller konsumenter) att läsa från kanalen. Att använda en sync.WaitGroup är ett enkelt sätt att vänta på att dessa arbetare ska avsluta springa.

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
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow