Конкурентность: горутины, каналы и контекст

Каналы и 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 та же программа работала бы даже без горутины, потому что отправка не блокируется сразу. Но это уже другая семантика: отправитель не знает, получено ли значение — он знает только, что оно положено в буфер. Для рандеву-синхронизации нужен именно небуферизованный канал.

Начать заново?

Помощник начнёт новый диалог по этой задаче. Текущая переписка останется в истории до конца срока хранения.

О переписке

Можно приложить до трёх фото PNG, JPEG или WebP по 5 МБ.

Переписка и фото хранятся 90 дней после последнего принятого сообщения. Преподаватель получит доступ к диалогу, когда вы попросите его помочь.

Проверка решения остаётся отдельным действием.