Каналы

В этой главе мы познакомимся с каналами

Все, что можно сделать при помощи каналов - можно сделать при помощи мьютексов. Каналы это просто другой, более удобный способ организации кода.

Канал это типизированная сущность в которую мы можем отправлять данные и из которой мы можем получать данные. Для того, чтобы начать работу с каналом его нужно создать при помощи ключевого слова make

// Создаем канал целых чисел
ch := make(chan int)

Для получения и оправки данных в канал используется оператор <-:

  • ch <- y - отправляем переменную y в канал ch
  • x := <- ch - читаем в новую переменную x из канала ch

Отправка или чтение из канала блокируют выполнение горутины пока другая сторона не готова


Можно сделать канал с буффером, в таком случае отправитель не будет блокирован, пока буфер не заполнится, а получатель пока буфер не будет пустой.

ch := make(chan int, 100) // <- можно записать 100 целых чисел, пока отправитель не будет блокирован

Прежде, чем избавиться от мьютексов в нашем приложении, рассмотрим несколько примеров использования каналов.

Пример 1.

func main() {
     c := make(chan int)
     c <- 100
     val := <-c
     fmt.Println(val)
}

Каков результат выполнения этой программы? Правильный ответ - программа упадет с ошибкой all goroutines are asleep - deadlock!` Потому, что операции с каналами без буфера блокируют выполнение горутин, до тех пор пока и отправитель и получатель не готовы обрабатывать данные.

Пример 2.


func main() {
    go func() {
        fmt.Println("я горутина")
    }()
    fmt.Println("я главная функция")
}

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

func main() {
    done := make(chan bool)
    go func() {
        fmt.Println("я горутина")
        done <- true
    }()
    fmt.Println("я главная функция")
    <-done
}

Здесь главная функция дождется выполнения горутины. Так как <-done блокирует выполнение главной функции, пока горутина не запишет туда значение.

Пример 3.

Данные из канала можно читать в цикле, используя ключевое слово range. Данные будут читаться пока канал не будет закрыт. при помощи клчевого слова close

func main() {
    message := make(chan string)
    go func() {
        for i := 1; i <= 3; i++ {
            message <- fmt.Sprintf("сообщение %d", i)
        }
        close(message)
    }()
    for msg := range message {
        fmt.Println(msg)
    }
}

Что выведет данный код? Что будет если убрать close(message)?

Пример 4.

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

func main() {
    done := make(chan bool)
    go func() {
        fmt.Println("я горутина")
        close(done)
    }()
    fmt.Println("я главная функция")
    <-done
}

Пример 5.

В реальном приложении каналов и горутин может быть много. Иногда возникает необходимость работать с множеством каналов.

func main() {
    c1 := time.Tick(time.Second)
    c2 := time.Tick(time.Second * 2)
    for {
        <-c1
        fmt.Println("c1")
        <-c2
        fmt.Println("c2")
    }
}

Функция Tick возвращает канал типа time.Time в который через указанный промежуток времени отправлятся текущее время. В результате выполнения данного кода мы получим:

c1
c2
c1
c2
c1
c2

Несмотря на то, что в канал c1 данные отправляются в два раза чаще чем в канал c2. Это происходит из-за блокировки каналом c2 цикла.

Для того чтобы ждать одновременно много каналов используется ключевое слово select.

func main() {
    c1 := time.Tick(time.Second)
    c2 := time.Tick(time.Second * 2)
    for {
        select {
        case <-c1:
            fmt.Println("c1")
        case <-c2:
            fmt.Println("c2")
        }
    }
}

Как только данные появятся в любом из каналов будет выполнен соответсвтующий case. Сравните вывод с использованием select

c1
c1
c2
c1
c1
c2
c1
c1
c2

После того, как мы изучили основы каналов перепишем наше приложение с использованием каналов.

results matching ""

    No results matching ""