Заметки по Go

справочник

Горутины и каналы

«Не общайтесь через разделяемую память — разделяйте память через общение». Основной инструмент — горутина и канал, а не мьютекс.

Горутины

Горутина — лёгкий поток, управляемый рантаймом Go. Запуск — ключевое слово go. Их могут быть сотни тысяч: стек начинается с пары килобайт и растёт по нужде.

func main() {
    go say("привет")   // в отдельной горутине
    say("мир")         // в текущей
}

func say(s string) {
    for i := 0; i < 3; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}

Каналы

Канал передаёт значения между горутинами и синхронизирует их. Отправка и приём блокируются, пока другая сторона не готова — если канал небуферизованный.

ch := make(chan int)        // небуферизованный
go func() { ch <- 42 }()    // отправка
v := <-ch                    // приём: 42

buf := make(chan int, 8)    // буфер на 8 значений

Закрытый канал больше не принимает отправку, но приём из него отдаёт оставшиеся значения, а затем нулевые. Идиома перебора — for range:

close(buf)
for v := range buf {
    fmt.Println(v)
}

select

select ждёт сразу на нескольких каналах — готовый выигрывает; если готовы несколько, выбор случайный. Ветка default делает операцию неблокирующей.

select {
case v := <-in:
    process(v)
case out <- result:
    // отправили
case <-time.After(time.Second):
    return errTimeout        // таймаут
}

Когда каналов мало: sync

Для простой защиты разделяемого состояния — sync.Mutex; дождаться группу горутин — sync.WaitGroup; выполнить единожды — sync.Once.

var wg sync.WaitGroup
for _, u := range urls {
    wg.Add(1)
    go func(u string) {
        defer wg.Done()
        fetch(u)
    }(u)
}
wg.Wait()   // ждём завершения всех

Отмену и дедлайны across горутин удобно нести через context.Context — он же связывает цепочку вызовов в HTTP-сервере.