TGViewer
Библиотека Go-разработчика | Golang Библиотека Go-разработчика | Golang @goproglib · 24.1K subscribers
Post #7310 2.4K
📎 Однонаправленные каналы ловят баги на компиляции

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
  • 👍 5
More from @goproglib
  1. Sep 26, 2026🔥 В Go 1.27 появился portable SIMD До этого SIMD-оптимизации в Go требовали архитектурног…
  2. Sep 25, 2026🤡🤡 📍 Навигация: Вакансии • Задачи • Собесы 🐸 Библиотека Go-разработчика #GoGiggle
  3. Sep 25, 2026💡 Код работает. А data race уже есть В Go можно записать значение в одной горутине, прочи…
  4. Sep 23, 2026💥 TCP/IP: что происходит с данными в сети Когда Go-приложение отправляет данные по сети,…
  5. Sep 22, 2026🔴 Встроенные функции len, make, panic — встроенные функции Go, которые можно использовать…
  6. Sep 21, 2026🤩 Go с нуля: серия базовых шпаргалок Собрали путь от Hello, World! до goroutines, channel…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →