Каналы и select: передача данных и координация событий
Содержание курса
В прошлом уроке горутины координировались через sync.WaitGroup и sync.Mutex: одна горутина ждала завершения других, а мьютекс защищал общий счётчик. Это работает, но требует явного разделения памяти и блокировок вокруг каждого чтения и записи. Каналы предлагают другой контракт: вместо того чтобы несколько горутин читали одну переменную по очереди, одна горутина передаёт значение другой — и передача сама по себе является синхронизацией.
make(chan T), буфер, отправка и приём: механика блокировки
Канал создаётся через make. Две формы:
ch := make(chan int) // небуферизованный
buf := make(chan int, 3) // буферизованный, ёмкость 3
Тип элементов фиксируется при создании: chan int не примет строку, компилятор это проверяет статически.
Посмотрим на небуферизованный случай. Если горутина A выполняет ch <- 42, она замирает — не в смысле sleep, а в смысле «планировщик её не будет трогать» — до тех пор, пока другая горутина не выполнит v := <-ch. Оба события должны случиться одновременно: это рандеву. Именно поэтому нельзя отправить в небуферизованный канал из единственной горутины без другого получателя — программа намертво зависнет.
Буфер меняет контракт: отправка buf <- 1 не блокирует, пока в буфере есть место. Отправитель заполнил три слота — на четвёртой попытке он снова блокируется, пока получатель не вытащит хотя бы одно значение.
Минимальный рабочий пример — обмен числом между двумя горутинами:
package main
import "fmt"
func main() {
ch := make(chan int)
go func() {
ch <- 7 // отправитель блокируется здесь
}()
v := <-ch // получатель разблокирует отправителя
fmt.Println(v) // 7
}
Последовательность: горутина пытается отправить 7, встаёт в ожидание; main доходит до <-ch, забирает значение — и в этот момент обе стороны продолжают выполнение. Канал здесь — точка встречи, а не хранилище.
С буфером ёмкостью 1 та же программа работала бы даже без горутины, потому что отправка не блокируется сразу. Но это уже другая семантика: отправитель не знает, получено ли значение — он знает только, что оно положено в буфер. Для рандеву-синхронизации нужен именно небуферизованный канал.
