#20.2 golang Однонаправленные каналы, объявление типа и инициализация - когда используются

Ранее мы начали говорить про каналы в golang и в тех примерах все они были двухнапраправленными - то есть мы могли писать в них и тут же читать, минималистичный пример кода работы с таким каналом будет выглядеть так:

package main

import (
	"fmt"
)

func main() {
	// объявление двунаправленного канала
	// сразу с инициализацией
	var intChannel chan int = make(chan int)

	go func() {
		intChannel <- 5 // пишем значение в канал
	}()

	val := <-intChannel // читаем значение
	fmt.Println("Значение из канала:", val)
}

Но мы можем определять каналы и только для чтения или только для отправки

Определение каналов только чтения (отправки) или только для записи (для получения)

  • Определение канала только для записи данных:
    var inputChannel chan <- int

    -- стрелка направлен "в канал" (смотрит на слово chan)

  • Определение канала только для чтения данных:
    var outputChannel <-  chan int

    -- стрелка направлен "из канала" (выходит из слова chan)

Также канал можно сразу иницилизировать только для чтения или только записи через make:

  • Создание канала только для записи данных:
    ch := make(chan <- int)
  • Создание канала только для чтения данных:
    ch := make(<- chan int)

-- но тут есть важный момент: сфера использования действительно однонаправленных каналов крайне узка.
Поэтому если объяление типа однонаправленного канала действительно используется часто, то вот реальное создание однонапраленного канала на уровене инициализации - довольно редкая ситуация.

Чаще мы всё-таки создаем канал как двунаправленный, а потом передаем его куда-то, где ожидается однонаправленный, рассмотрим пример:

package main

import (
	"fmt"
)

/*
Здесь ожидаем на вход канал "только для записи"

Это значит, что внутри функции
мы можем только писать в канал,
но не читать из него
*/
func send(channel chan<- int) {
	channel <- 22 // отправляем данные
}

func main() {

	// обычный двунаправленный канал
	channel := make(chan int)
	go send(channel) // запускаем горутину для записи
	fmt.Println(<-channel) // читаем значение
}

-- фактически тут разработчики golang разрешают неявное приведение типа для каналов.

По аналогии можно объявить в функции и канал только для чтения данных (с синхронизацией через sync.WaitGroup):

package main

import (
	"fmt"
	"sync"
)

// Ожидаем канал, доступный только для чтения
func read(channel <-chan int, wg *sync.WaitGroup) {
	fmt.Println(<-channel)
	wg.Done() // сигнализируем, что горутина завершилась
}

func main() {

	var wg sync.WaitGroup
	channel := make(chan int)
	wg.Add(1)             // будем ждать выполнения еще 1 горутины
	go read(channel, &wg) // запускаем горутину

	channel <- 5 // пишем в канал

	wg.Wait() // ждем завершения горутины
}

-- обратите внимание, что здесь мы:

  1. сначала вызваем дополнительную горутину,
  2. и только потом пишем в канал, т.к. заполненный канал, блокирует текущую горутину в том числе и после записи, пока кто-нибудь не считает значение (чтобы этот кто-нибудь был - мы заранее и запускаем фоновую горутину).

Источники