Go Academy · Урок

Направление каналов и шаблоны конвейеров

Каналы только для чтения и записи, а также конвейеры

Урок 4 из 412 шагов

«Направление каналов и шаблоны конвейеров» — бесплатный урок 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 — локальная установка не требуется.

Все уроки этого курса

  1. Запуск горутин
  2. Небуферизованные каналы
  3. Буферизованные каналы
  4. Направление каналов и шаблоны конвейеров
← Назад к Go Academy