← Все этапы
05 · КОНКУРЕНТНОСТЬУрок 31 из 3324 минуты

Каналы и select

Передаём результаты между горутинами, сравниваем буферизированный и небуферизированный канал.

Канал передаёт значения

Канал передаёт типизированные значения между горутинами. Операция отправки записывается как ch <- value, получение — как value := <-ch. У небуферизированного канала отправитель ждёт, пока получатель будет готов принять значение; это синхронная передача.

Буферизированный канал создают с ёмкостью, например make(chan int, 2). Он принимает до двух ещё не прочитанных значений. После заполнения следующая отправка блокируется, пока получатель не освободит место. «Буферизированный» не означает «неблокирующий при любом числе отправок».

Ключевое слово select ждёт готовности одной из перечисленных операций отправки или получения. Если готовы несколько веток, выбирается одна из них без приоритета; нельзя считать, что всегда сработает первая ветка. Для выбора с таймаутом часто добавляют ветку с каналом таймера. Канал закрывает отправитель, когда новых значений больше не будет. Получение из закрытого канала сразу возвращает нулевое значение и ok == false.

Пример

package main

import (
	"fmt"
	"sort"
)

func publish(values ...int) <-chan int {
	items := make(chan int, len(values))
	go func() {
		defer close(items)
		for _, value := range values {
			items <- value
		}
	}()
	return items
}

func main() {
	left := publish(8, 2)
	right := publish(7, 3)
	values := make([]int, 0, 4)

	for left != nil || right != nil {
		select {
		case value, ok := <-left:
			if !ok {
				left = nil
				continue
			}
			values = append(values, value)
		case value, ok := <-right:
			if !ok {
				right = nil
				continue
			}
			values = append(values, value)
		}
	}

	sort.Ints(values)
	fmt.Println(values)
}

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

В цикле select получает значение из любого готового канала. Когда один закрывается, присваивание nil отключает его ветку: операция получения из nil-канала никогда не готова. После завершения обоих потоков сортировка устраняет неопределённость порядка выбора и программа печатает [2 3 7 8].

Практика

Попробуйте сами

Создай два производителя целых чисел с буферизированными каналами ёмкости 3. Первый отправляет 9, 1, 5, второй — 8, 2, 4, затем каждый закрывает свой канал. Собирай значения через select, прекращай выбирать закрытые каналы и напечатай общий отсортированный срез. После кода кратко объясни, что произойдёт при четвёртой отправке до чтения из заполненного канала.

Проверка результата

Как понять, что получилось

Программа печатает [1 2 4 5 8 9] при каждом запуске и завершает обе горутины. Каждый канал закрывает только его производитель. В пояснении сказано, что четвёртая невычитанная отправка в канале ёмкости 3 блокируется до чтения.

Каналы и select | Go | WebSchool · Go