Направление каналов и шаблоны конвейеров
Каналы только для чтения и записи, а также конвейеры
«Направление каналов и шаблоны конвейеров» — бесплатный урок Go Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Go Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Go Academy содержит 4 уроков всего.
Однонаправленные типы каналов
Go позволяет ограничивать каналы режимом только отправки или только получения в сигнатурах типов. Это документирует назначение и предотвращает неправильное использование:
package main
// send-only: can only send, not receive or close (close is allowed by sender)
func sender(out chan<- int) {
out <- 42
}
// receive-only: can only receive, not send or close
func receiver(in <-chan int) int {
return <-in
}
// bidirectional: can do both
func relay(in <-chan int, out chan<- int) {
out <- <-in
}Преобразование направления канала
Двунаправленный канал можно неявно преобразовать в любой однонаправленный тип, но обратное преобразование невозможно:
package main
import "fmt"
func produce(out chan<- int) { out <- 1; close(out) }
func consume(in <-chan int) { fmt.Println(<-in) }
func main() {
ch := make(chan int) // bidirectional
go produce(ch) // auto-converts to chan<- int
consume(ch) // auto-converts to <-chan int
// chan<- int cannot be converted back to <-chan int
}Pipeline: этап 1 — генератор
pipeline объединяет goroutine в цепочку, где каждый этап читает из выходного канала предыдущего этапа. Генератор — первый этап:
package main
import "fmt"
func generate(nums ...int) <-chan int {
out := make(chan int)
go func() {
for _, n := range nums {
out <- n
}
close(out)
}()
return out
}
func main() {
ch := generate(2, 3, 4, 5)
for v := range ch { fmt.Println(v) }
}Pipeline: этап 2 — преобразование
Промежуточные этапы получают данные из одного канала, преобразуют их и отправляют в другой:
package main
import "fmt"
func generate(nums ...int) <-chan int {
out := make(chan int)
go func() { for _, n := range nums { out <- n }; close(out) }()
return out
}
func square(in <-chan int) <-chan int {
out := make(chan int)
go func() {
for v := range in { out <- v * v }
close(out)
}()
return out
}
func main() {
for v := range square(generate(2, 3, 4, 5)) {
fmt.Println(v) // 4, 9, 16, 25
}
}Pipeline: полный пример из трёх этапов
Объединение трёх этапов: генерация, преобразование и фильтрация:
package main
import "fmt"
func gen(nums ...int) <-chan int {
c := make(chan int)
go func() { for _, n := range nums { c <- n }; close(c) }()
return c
}
func double(in <-chan int) <-chan int {
c := make(chan int)
go func() { for v := range in { c <- v * 2 }; close(c) }()
return c
}
func evens(in <-chan int) <-chan int {
c := make(chan int)
go func() { for v := range in { if v%2 == 0 { c <- v } }; close(c) }()
return c
}
func main() {
for v := range evens(double(gen(1,2,3,4,5))) {
fmt.Println(v) // 2, 4, 6, 8, 10
}
}Отмена pipeline с каналом done
Передайте канал done, чтобы этапы pipeline могли завершиться досрочно при отмене:
package main
import "fmt"
func gen(done <-chan struct{}, nums ...int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for _, n := range nums {
select {
case out <- n:
case <-done: // cancel signal
return
}
}
}()
return out
}
func main() {
done := make(chan struct{})
ch := gen(done, 1, 2, 3, 4, 5)
fmt.Println(<-ch) // 1
close(done) // cancel remaining work
fmt.Println("pipeline cancelled")
}Распределение работы
Распределение работы направляет данные из одного канала нескольким goroutine для параллельной обработки:
package main
import ("fmt"; "sync")
func fanOut(in <-chan int, workers int) []<-chan int {
outs := make([]<-chan int, workers)
for i := 0; i < workers; i++ {
out := make(chan int)
outs[i] = out
go func() {
for v := range in { out <- v * v } // each worker processes
close(out)
}()
}
return outs
}
func main() {
fmt.Println("fan-out distributes work to multiple goroutines")
_ = sync.WaitGroup{}
}Объединение каналов
Сведение объединяет несколько каналов в один:
package main
import ("fmt"; "sync")
func merge(cs ...<-chan int) <-chan int {
out := make(chan int)
var wg sync.WaitGroup
for _, c := range cs {
wg.Add(1)
c := c
go func() { defer wg.Done(); for v := range c { out <- v } }()
}
go func() { wg.Wait(); close(out) }()
return out
}
func gen(nums ...int) <-chan int {
c := make(chan int)
go func() { for _, n := range nums { c <- n }; close(c) }()
return c
}
func main() {
for v := range merge(gen(1,2), gen(3,4), gen(5,6)) {
fmt.Println(v)
}
}Лучшие практики работы с конвейерами
Принципы проектирования конвейера:
- Каждый этап: горутина читает из
<-chan, записывает вchan<-и после завершения закрывает выходной канал - Канал закрывает только производитель — потребитель никогда этого не делает
- Передавайте канал
done(или контекст) для отмены - Используйте направленные типы каналов во всех сигнатурах функций
- При отмене опустошайте каналы, чтобы предотвратить утечки горутин
Практический конвейер: обработка CSV
Конвейеры особенно эффективны при обработке данных: чтении строк, разборе, фильтрации и преобразовании:
package main
import ("fmt"; "strings")
func splitLines(data string) <-chan string {
out := make(chan string)
go func() {
for _, line := range strings.Split(data, "\n") {
if line != "" { out <- line }
}
close(out)
}()
return out
}
func main() {
csv := "alice,30\nbob,25\ncarol,35"
for line := range splitLines(csv) {
parts := strings.Split(line, ",")
fmt.Printf("name=%s age=%s\n", parts[0], parts[1])
}
}Проверка знаний
Для чего нужны направленные типы каналов в сигнатурах функций?
Итоги: направление каналов и конвейеры
Краткое содержание:
chan<- T— только для отправки,<-chan T— только для получения: назначение документировано, а безопасность обеспечивается на этапе компиляции- Конвейеры: цепочки горутин, где каждая читает из одного канала и записывает в другой
- Распределение: один входной канал → несколько горутин
- Сведение: несколько каналов → один объединённый канал
- Всегда закрывайте каналы со стороны отправителя; для отмены используйте канал
done
Изучай Go с ИИ-репетитором — бесплатно
Пиши и запускай код прямо в браузере, получай мгновенную помощь от ИИ-репетитора 24/7 и продолжи учиться на сайте или в приложении.
- Курсы
- 51
- Уроки
- 203
Часто задаваемые вопросы
Урок «Направление каналов и шаблоны конвейеров» бесплатный?
Да — полный текст урока «Направление каналов и шаблоны конвейеров» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Go Academy, подпишись на CoddyKit PRO. Курс Go Academy содержит 4 уроков всего.
Чему я научусь в уроке «Направление каналов и шаблоны конвейеров»?
Каналы только для чтения и записи, а также конвейеры Ты практикуешь Go Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Go Academy?
Предыдущий опыт не требуется. Go Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.
Сколько времени занимает урок «Направление каналов и шаблоны конвейеров»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Go Academy?
Да. Каждый урок Go Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Запуск горутин
- Небуферизованные каналы
- Буферизованные каналы
- Направление каналов и шаблоны конвейеров