Go позволяет объявлять каналы только для отправки (
chan<- Job) или только для приёма (<-chan Job). В большинстве кодовых баз это не используют, функции просто принимают chan Job.Формально это допустимо, но вы теряете кое-что важное. Компилятор больше не может подсказать вам, что поток данных идёт неправильно.
➡️ В чём боль
Вот пул, где путаница с направлением приводит к багу:
func spawnWorkers(jobs chan Job, results chan Result) {
for i := 0; i < 10; i++ {
go func() {
for job := range jobs {
result := process(job)
jobs <- result.nextJob // воркер пишет обратно во входной канал
results <- result
}
}()
}
}Воркер случайно записал данные в
jobs вместо results. С двунаправленными каналами это компилируется без жалоб, и баг живёт в проде. С направленными типами падает уже на компиляции:func spawnWorkers(jobs <-chan Job, results chan<- Result) {
for i := 0; i < 10; i++ {
go func() {
for job := range jobs {
result := process(job)
jobs <- result.nextJob // ошибка компиляции: нельзя писать в канал только для приёма
results <- result
}
}()
}
}Направленные типы работают как документация, которую проверяет компилятор. Продюсер видит только
chan<- Job. Консьюмер видит только <-chan Job. Ни один не сможет сделать что-то не то по ошибке.➡️ Как чинить
Полный воркер-пул с правильным применением этого приёма:
func produce(jobs chan<- Job, jobList []Job) {
defer close(jobs)
for _, job := range jobList {
jobs <- job
}
}
func consume(jobs <-chan Job, results chan<- Result, wg *sync.WaitGroup) {
defer wg.Done()
for job := range jobs {
results <- process(job)
}
}
func run(jobList []Job) []Result {
jobs := make(chan Job, 100)
results := make(chan Result, 100)
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go consume(jobs, results, &wg)
}
go produce(jobs, jobList)
// Закрываем results, когда все воркеры закончили
go func() {
wg.Wait()
close(results)
}()
var out []Result
for result := range results {
out = append(out, result)
}
return out
}Каждая функция теперь видит только то, что ей положено. Компилятор следит за потоком данных. Читается всё легко.
produce подаёт задачи, consume их разгребает, отдельная горутина закрывает results, когда все воркеры завершились.Указывайте направление каналов на границах функций через
chan<- и <-chan. Это бесплатная документация, которую вдобавок проверяет компилятор. Тут даже тест не нужен, всё ловится при сборке. Привычка простая, а целый класс багов с записью не в ту сторону исчезает сам собой.📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека Go-разработчика
#GoToProduction