Szukaj…


Wprowadzenie

Kanał zawiera wartości danego typu. Wartości mogą być zapisywane na kanale i odczytywane z niego, i krążą w kanale w kolejności „pierwsze w pierwsze”. Istnieje rozróżnienie między buforowanymi kanałami, które mogą zawierać kilka komunikatów, a niebuforowanymi kanałami, które nie mogą. Kanały są zwykle używane do komunikacji między goroutynami, ale są również przydatne w innych okolicznościach.

Składnia

  • make (chan int) // utwórz niebuforowany kanał
  • make (chan int, 5) // utwórz buforowany kanał o pojemności 5
  • close (ch) // zamyka kanał „ch”
  • ch <- 1 // zapisz wartość 1 na kanale „ch”
  • val: = <-ch // odczyt wartości z kanału „ch”
  • val, ok: = <-ch // alternatywna składnia; ok to bool wskazujący, czy kanał jest zamknięty

Uwagi

Kanał z pustą strukturą make(chan struct{}) jest jasnym komunikatem dla użytkownika, że żadna informacja nie jest przesyłana kanałem i że jest on wykorzystywany wyłącznie do synchronizacji.

W odniesieniu do niebuforowanych kanałów, zapis kanału będzie blokowany, dopóki odpowiedni odczyt nie nastąpi z innego goroutine. To samo dotyczy blokowania odczytu kanału podczas oczekiwania na pisarz.

Korzystanie z zasięgu

Podczas odczytywania wielu wartości z kanału używanie range jest powszechnym wzorcem:

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

Plac zabaw

Wynik

1
2
3
channel is now closed

Limit czasu

Kanały są często używane do wdrażania limitów czasu.

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

Koordynujące goroutine

Wyobraź sobie goroutine z dwuetapowym procesem, w którym główny wątek musi wykonać trochę pracy między każdym krokiem:

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
}

Buforowane a niebuforowane

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

idź na plac zabaw

Blokowanie i odblokowywanie kanałów

Domyślnie komunikacja za pośrednictwem kanałów jest synchronizowana; kiedy wysyłasz jakąś wartość, musi być odbiornik. W przeciwnym razie pojawi się fatal error: all goroutines are asleep - deadlock! następująco:

package main

import "fmt"

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

Bu istnieje rozwiązanie: użyj buforowanych kanałów:

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

Oczekiwanie na zakończenie pracy

Częstą techniką korzystania z kanałów jest tworzenie pewnej liczby pracowników (lub konsumentów) do czytania z kanału. Korzystanie z synchronizacji.WaitGroup to łatwy sposób oczekiwania na zakończenie pracy przez pracowników.

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
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow