Recherche…


Introduction

Un canal contient des valeurs d'un type donné. Les valeurs peuvent être écrites sur un canal et en être lues, et elles circulent à l’intérieur du canal dans l’ordre du premier entré, premier sorti. Il existe une distinction entre les canaux tamponnés, qui peuvent contenir plusieurs messages, et les canaux non tamponnés, ce qui est impossible. Les canaux sont généralement utilisés pour communiquer entre les goroutines, mais sont également utiles dans d'autres circonstances.

Syntaxe

  • make (chan int) // crée un canal sans tampon
  • make (chan int, 5) // crée un canal en mémoire tampon d'une capacité de 5
  • close (ch) // ferme un canal "ch"
  • ch <- 1 // écrit la valeur de 1 dans un canal "ch"
  • val: = <-ch // lit une valeur du canal "ch"
  • val, ok: = <-ch // syntaxe alternative; ok est un bool indiquant si le canal est fermé

Remarques

Un canal contenant la structure vide make(chan struct{}) indique clairement à l'utilisateur qu'aucune information n'est transmise sur le canal et qu'elle est utilisée uniquement pour la synchronisation.

En ce qui concerne les canaux non tamponnés, une écriture de canal bloquera jusqu'à ce qu'une lecture correspondante se produise à partir d'une autre goroutine. La même chose est vraie pour un blocage de lecture de canal en attendant un écrivain.

Utiliser la gamme

Lors de la lecture de plusieurs valeurs d'un canal, l'utilisation de la range est un modèle courant:

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

Cour de récréation

Sortie

1
2
3
channel is now closed

Des délais d'attente

Les canaux sont souvent utilisés pour implémenter les délais d'attente.

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

Coordination des goroutines

Imaginez une goroutine avec un processus en deux étapes, où le thread principal doit travailler entre chaque étape:

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
}

Tamponné vs non tamponné

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

aller au terrain de jeux

Blocage et déblocage des canaux

Par défaut, la communication sur les canaux est synchronisée; Lorsque vous envoyez une valeur, il doit y avoir un récepteur. Sinon, vous aurez fatal error: all goroutines are asleep - deadlock! comme suit:

package main

import "fmt"

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

Il existe une solution: utiliser des canaux tamponnés:

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

En attendant que le travail se termine

Une technique courante pour utiliser des canaux consiste à créer un certain nombre de travailleurs (ou de consommateurs) à lire depuis le canal. L'utilisation d'un sync.WaitGroup est un moyen simple d'attendre que ces travailleurs aient fini de fonctionner.

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
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow