Go: каналы

Go позволяет выполнять десятки тысяч goroutine одновременно при этом не предъявляя к оборудованию значительных требований. Это является следствием дизайна goroutine в языке go. При разработке прикладного программного обеспечения постоянно требуется организоввывать обмен данными между разными вычислительными блоками программы и организоввывать синхронизация между ними. В Go одним из таких механизмов являются каналы. Каналы обеспечивают возможность общения нескольких горутин друг с другом, чтобы синхронизировать их выполнение. Простейший пример использования каналов ниже:

 package main

 import "fmt"

 func main() {
    messages := make(chan string)

    go func() { messages <- "ping" }()

    msg := <-messages
    fmt.Println(msg)
 }

Канал создается при помощи функции make. Тип канала представлен ключевым словом chan, за которым следует тип, который будет передаваться по каналу (в данном случае мы передаем строки). Оператор <-(стрелка влево) используется для отправки и получения сообщений по каналу. Конструкция messages <- "ping" означает отправку "ping", а msg := <- messages — его получение и сохранение в переменную msg. Строка с fmt может быть записана другим способом: fmt.Println(<-messages  ), тогда можно было бы удалить предыдущую строку.

Данное использование каналов позволяет синхронизировать две горутины. По умолчанию блок получения данных из канала и отправки в канал блокируются до того момента пока оба блока не будут готовы. Что позволяет использовать канал как объект синхронизации.

Мы можем задать направление передачи сообщений в канале, сделав его только отправляющим или принимающим. Например, так:

func func1(c chan<- string) {
}

или так

func func2(c <-chan string) {
}

В языке Go есть специальный оператор select который работает как switch, но для каналов:

package main

import "fmt"
import "time"

func main() {
    c1 := make(chan string)
    c2 := make(chan string)

    go func() {
        for {
            c1 <- "from 1"
            time.Sleep(time.Second * 2)
        }
    }()
    go func() {
        for {
            c2 <- "from 2"
            time.Sleep(time.Second * 3)
        }
    }()
    go func() {
        for {
            select {
            case msg1 := <- c1:
                fmt.Println(msg1)
            case msg2 := <- c2:
                fmt.Println(msg2)
            }
        }
    }()

    var input string
    fmt.Scanln(&input)
}

Эта программа выводит «from 1» каждые 2 секунды и «from 2» каждые 3 секунды. 

Оператор select выбирает первый готовый канал, и получает сообщение из него, или же передает сообщение через него. Когда готовы несколько каналов, получение сообщения происходит из случайно выбранного готового канала. Если же ни один из каналов не готов, оператор блокирует ход программы до тех пор, пока какой-либо из каналов будет готов к отправке или получению.

Обычно select используется для таймеров:

select {
case msg1 := <- c1:
    fmt.Println("Message 1", msg1)
case msg2 := <- c2:
    fmt.Println("Message 2", msg2)
case <- time.After(time.Second):
    fmt.Println("timeout")
}

time.After создаёт канал, по которому посылаем метки времени с заданным интервалом. В данном случае мы не заинтересованы в значениях временных меток, поэтому мы не сохраняем его в переменные. Также мы можем задать команды, которые выполняются по умолчанию, используя конструкцию default:

select {
case msg1 := <- c1:
    fmt.Println("Message 1", msg1)
case msg2 := <- c2:
    fmt.Println("Message 2", msg2)
case <- time.After(time.Second):
    fmt.Println("timeout")
default:
    fmt.Println("nothing ready")
}

Выполняемые по умолчанию команды исполняются сразу же, если все каналы заняты.

При инициализации канала можно использовать второй параметр:

c := make(chan int, 1)

и мы получим буферизированный канал с ёмкостью 1. Обычно каналы работают синхронно - каждая из сторон ждёт, когда другая сможет получить или передать сообщение. Но буферизованный канал работает асинхронно — получение или отправка сообщения не заставляют стороны останавливаться. Но канал теряет пропускную способность, когда он занят, в данном случае, если мы отправим в канал 1 сообщение, то мы не сможем отправить туда ещё одно до тех пор, пока первое не будет получено.

package main
import "fmt"
func main() {
 
    messages := make(chan string, 2)
 
    messages <- "buffered"
 
    fmt.Println(<-messages)
    fmt.Println(<-messages)
}

 

04.01.2017









 
архив

подписка