Buscar..


Introducción

Un canal contiene valores de un tipo dado. Los valores se pueden escribir en un canal y leer desde él, y circulan dentro del canal en orden de primero en entrar, primero en salir. Hay una distinción entre los canales almacenados en búfer, que pueden contener varios mensajes, y los canales no almacenados, que no pueden. Los canales se usan normalmente para comunicarse entre goroutines, pero también son útiles en otras circunstancias.

Sintaxis

  • make (chan int) // crea un canal sin búfer
  • make (chan int, 5) // crea un canal con buffer con una capacidad de 5
  • cerrar (ch) // cierra un canal "ch"
  • ch <- 1 // escribe el valor de 1 en un canal "ch"
  • val: = <-ch // lee un valor del canal "ch"
  • val, ok: = <-ch // sintaxis alternativa; ok es un bool que indica si el canal está cerrado

Observaciones

Un canal que contiene la estructura vacía make(chan struct{}) es un mensaje claro para el usuario de que no se transmite información a través del canal y que se utiliza exclusivamente para la sincronización.

Con respecto a los canales no almacenados, la escritura de un canal se bloqueará hasta que se produzca la lectura correspondiente de otro goroutine. Lo mismo es cierto para un bloqueo de lectura de canal mientras se espera un escritor.

Utilizando rango

Al leer varios valores de un canal, el uso de range es un patrón común:

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

Patio de recreo

Salida

1
2
3
channel is now closed

Tiempos de espera

Los canales se utilizan a menudo para implementar tiempos de espera.

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

Coordinadores goroutines

Imagine un goroutine con un proceso de dos pasos, donde el hilo principal debe hacer algo de trabajo entre cada paso:

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
}

Buffer vs vs no buffer

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

ir al patio

Bloqueo y desbloqueo de canales.

Por defecto, la comunicación a través de los canales está sincronizada; Cuando envías algún valor debe haber un receptor. De lo contrario, obtendrás un fatal error: all goroutines are asleep - deadlock! como sigue:

package main

import "fmt"

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

Pero hay una solución de uso: usar canales en búfer:

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

Esperando que el trabajo termine

Una técnica común para usar canales es crear una cantidad de trabajadores (o consumidores) para leer desde el canal. Usar un sync.WaitGroup es una manera fácil de esperar a que esos trabajadores terminen de correr.

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
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow