Конкурентность — Примеры
Пример 1 — Пул, который всегда завершается
Задание импорта проходит по списку заказов и на каждом вызывает медленный поиск, а первая версия заводит горутину на заказ и возвращается сразу после обхода списка, поэтому процесс успевает завершиться на половине работы. Нужна версия с фиксированным числом работников, которая возвращается только тогда, когда обработан последний заказ.
for _, o := range orders {
go enrich(o) // никто их не ждёт
}
return nil
Решение
jobs := make(chan Order)
var wg sync.WaitGroup
for i := 0; i < workers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for o := range jobs {
enrich(o)
}
}()
}
for _, o := range orders {
jobs <- o
}
close(jobs) // закрывает отправитель, никогда приёмник
wg.Wait()
О том, что заказов больше не будет, каждому работнику сообщает закрытие канала. О завершении работы сообщает группа ожидания, поэтому функция возвращается только после последнего заказа. В первой версии на каждый заказ заводится своя горутина, нигде не учтённая, и каждая незавершённая держит свой стек. А канал, закрытый приёмником, вызывает панику на следующей отправке — именно на этом обычно и ошибаются.
Пример 2 — Кто ждёт, когда очередь заполняется
Читатель производит события быстрее, чем писатель успевает их сохранять; буфер добавили, чтобы сгладить всплески, а память растёт, пока процесс не убьют. Выберите буфер и политику на случай заполнения и напишите версию, которая их применяет.
var events []Event
for e := range source {
events = append(events, e) // растёт без ограничения
write(e)
}
Решение
// 32 события поглощаются без ожидания, затем производитель замедляется.
queue := make(chan Event, 32)
go writeAll(queue)
for e := range source {
queue <- e // при заполнении эта отправка ждёт
}
close(queue)
Буфер указанного размера разъединяет прибытие и обработку до этого размера, а после него отправка ждёт и читатель удерживается на скорости писателя; это осознанный выбор вместо отбрасывания событий. Однако неограниченный срез — не буфер побольше, а рост памяти без потолка. Обычно ошибаются иначе: размер буфера подбирают, чтобы спрятать всплеск нагрузки, вместо того чтобы решить и записать, что происходит при заполнении.