Поиск…


Вступление

Канал содержит значения заданного типа. Значения могут быть записаны на канал и считаны из него, и они распространяются внутри канала в порядке «первым в первом порядке». Существует различие между буферными каналами, которые могут содержать несколько сообщений и небуферизованные каналы, что не может. Каналы обычно используются для связи между goroutines, но также полезны в других обстоятельствах.

Синтаксис

  • make (chan int) // создание небуферизованного канала
  • make (chan int, 5) // создать буферный канал с емкостью 5
  • close (ch) // закрывает канал "ch"
  • ch <- 1 // записываем значение 1 в канал "ch"
  • val: = <-ch // считывает значение из канала "ch"
  • val, ok: = <-ch // альтернативный синтаксис; ok - это bool, указывающий, закрыт ли канал

замечания

Канал, содержащий пустую структуру struct make(chan struct{}) является ясным сообщением пользователю о том, что никакая информация не передается по каналу и что она используется исключительно для синхронизации.

Что касается небуферизованных каналов, запись канала будет блокироваться до тех пор, пока соответствующее прочтение не поступит от другого горутина. То же самое верно для блокировки чтения канала во время ожидания записи.

Использование диапазона

При чтении нескольких значений из канала использование range - это общий шаблон:

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

Детская площадка

Выход

1
2
3
channel is now closed

Таймауты

Каналы часто используются для реализации тайм-аутов.

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

Координация горти

Представьте себе горутин с двухэтапным процессом, где основной поток должен выполнять некоторую работу между каждым шагом:

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
}

Буферизованный и небуферизованный

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

пойти на площадку

Блокирование и разблокирование каналов

По умолчанию связь по каналам синхронизируется; когда вы отправляете какое-то значение, должен быть приемник. В противном случае вы получите fatal error: all goroutines are asleep - deadlock! следующее:

package main

import "fmt"

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

Бу используется решение: используйте буферные каналы:

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

Ожидание работы до конца

Общей методикой использования каналов является создание некоторого числа рабочих (или потребителей) для чтения с канала. Использование sync.WaitGroup - это простой способ дождаться завершения работы этих рабочих.

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
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow