Ricerca…


introduzione

Un canale contiene valori di un determinato tipo. I valori possono essere scritti su un canale e letti da esso, e circolano all'interno del canale in ordine first-in-first-out. Vi è una distinzione tra i canali bufferizzati, che possono contenere più messaggi e canali non bufferizzati, che non possono. I canali vengono in genere utilizzati per comunicare tra le goroutine, ma sono anche utili in altre circostanze.

Sintassi

  • make (chan int) // crea un canale unbuffered
  • make (chan int, 5) // crea un canale bufferizzato con una capacità di 5
  • chiudi (ch) // chiude un canale "ch"
  • ch <- 1 // scrivi il valore di 1 in un canale "ch"
  • val: = <-ch // legge un valore dal canale "ch"
  • val, ok: = <-ch // sintassi alternativa; ok è un bool che indica se il canale è chiuso

Osservazioni

Un canale che tiene la struct vuota make(chan struct{}) è un messaggio chiaro all'utente che nessuna informazione è trasmessa sul canale e che è puramente usata per la sincronizzazione.

Per quanto riguarda i canali non bufferizzati, una scrittura di canale si bloccherà fino a quando non si verificherà una lettura corrispondente da un'altra goroutine. Lo stesso vale per un blocco di lettura del canale in attesa di uno scrittore.

Usando la gamma

Quando si leggono più valori da un canale, l'utilizzo range è un modello comune:

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

Terreno di gioco

Produzione

1
2
3
channel is now closed

timeout

I canali sono spesso usati per implementare i timeout.

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

Goroutine di coordinamento

Immagina una goroutine con un processo in due passaggi, in cui il thread principale deve fare un po 'di lavoro tra ogni passaggio:

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
}

Buffered vs unbuffered

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

vai al parco giochi

Blocco e sblocco dei canali

Di default la comunicazione sui channcels è sincronizzata; quando invii qualche valore ci deve essere un ricevitore. Altrimenti si verificherà un fatal error: all goroutines are asleep - deadlock! come segue:

package main

import "fmt"

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

Bu c'è una soluzione uso: utilizzare i canali bufferizzati:

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

Aspettando che il lavoro finisca

Una tecnica comune per l'utilizzo dei canali consiste nel creare un certo numero di lavoratori (o consumatori) da leggere dal canale. Usare un sync.WaitGroup è un modo semplice per aspettare che quei lavoratori finiscano di correre.

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
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow