← Назад к вопросам
Pipeline: возведение в квадрат
1.3 Junior🔥 121 комментариев
#Основы Go
Условие
Даны два канала. В первый канал пишутся числа. Нужно читать числа из первого канала, возводить их в квадрат и записывать результат во второй канал.
Сигнатура
func squarePipeline(input <-chan int, output chan<- int)
Требования
- Читать числа из input по мере поступления
- Возводить в квадрат
- Записывать результат в output
- Корректно завершаться при закрытии input канала
- Закрывать output канал после обработки всех данных
Пример использования
input := make(chan int)
output := make(chan int)
go squarePipeline(input, output)
go func() {
for i := 1; i <= 5; i++ {
input <- i
}
close(input)
}()
for result := range output {
fmt.Println(result) // 1, 4, 9, 16, 25
}
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение
Pipeline — это паттерн для обработки данных через последовательные этапы. Каждый этап принимает данные из входного канала, обрабатывает и отправляет в выходной канал.
Подход
- Читаем данные из
inputканала черезrange - Для каждого значения:
- Вычисляем квадрат
- Отправляем в
outputканал
- Когда
inputзакрывается,rangeзавершается - Закрываем
outputканал (сигнал о завершении)
Реализация
func squarePipeline(input <-chan int, output chan<- int) {
defer close(output) // гарантирует закрытие output
for num := range input { // читаем из input пока он не закрыт
output <- num * num // отправляем квадрат в output
}
}
Пошаговый пример
func main() {
input := make(chan int)
output := make(chan int)
// Запускаем pipeline в background
go squarePipeline(input, output)
// Отправляем данные
go func() {
for i := 1; i <= 5; i++ {
input <- i // отправляем 1, 2, 3, 4, 5
}
close(input) // сигнал: больше данных нет
}()
// Читаем результаты
for result := range output {
fmt.Println(result) // выводит: 1, 4, 9, 16, 25
}
}
Выполнение:
1. squarePipeline начинает ждать данных из input
2. Горутина отправляет 1 → pipeline вычисляет 1² = 1 → отправляет в output
3. Main читает 1
4. Горутина отправляет 2 → pipeline вычисляет 2² = 4 → отправляет в output
5. Main читает 4
... (повторяется для 3, 4, 5)
6. Горутина закрывает input
7. squarePipeline range завершается
8. defer close(output) закрывает output
9. Main range завершается (канал закрыт)
Полный код с примерами
package main
import "fmt"
func squarePipeline(input <-chan int, output chan<- int) {
defer close(output)
for num := range input {
output <- num * num
}
}
func main() {
input := make(chan int)
output := make(chan int)
go squarePipeline(input, output)
// Отправляем данные
go func() {
for i := 1; i <= 5; i++ {
input <- i
}
close(input)
}()
// Читаем результаты
for result := range output {
fmt.Println(result) // 1, 4, 9, 16, 25
}
}
Вариант с буферизованными каналами
func main() {
input := make(chan int, 5) // буферизованный
output := make(chan int, 5) // буферизованный
go squarePipeline(input, output)
// Отправляем все сразу
for i := 1; i <= 5; i++ {
input <- i
}
close(input)
// Читаем результаты
for result := range output {
fmt.Println(result)
}
}
Цепочка pipelines (более сложный пример)
Можно создать несколько этапов обработки:
// Этап 1: возведение в квадрат
func square(input <-chan int, output chan<- int) {
defer close(output)
for num := range input {
output <- num * num
}
}
// Этап 2: умножение на 2
func double(input <-chan int, output chan<- int) {
defer close(output)
for num := range input {
output <- num * 2
}
}
func main() {
// Создаём каналы
input := make(chan int)
middle := make(chan int)
output := make(chan int)
// Запускаем этапы
go square(input, middle) // input → square → middle
go double(middle, output) // middle → double → output
// Отправляем данные
go func() {
for i := 1; i <= 5; i++ {
input <- i
}
close(input)
}()
// Читаем результаты
for result := range output {
fmt.Println(result) // (1²)*2=2, (2²)*2=8, (3²)*2=18, ...
}
}
Pipeline с обработкой ошибок
type Result struct {
Value int
Error error
}
func squarePipelineWithError(input <-chan int, output chan<- Result) {
defer close(output)
for num := range input {
if num < 0 {
output <- Result{Error: fmt.Errorf("negative number: %d", num)}
} else {
output <- Result{Value: num * num}
}
}
}
func main() {
input := make(chan int)
output := make(chan Result)
go squarePipelineWithError(input, output)
go func() {
for _, i := range []int{1, -2, 3, 4} {
input <- i
}
close(input)
}()
for result := range output {
if result.Error != nil {
fmt.Printf("Ошибка: %v\n", result.Error)
} else {
fmt.Printf("Результат: %d\n", result.Value)
}
}
}
Важные детали
1. defer close(output) Гарантирует закрытие канала даже при паник:
func squarePipeline(input <-chan int, output chan<- int) {
defer close(output) // ВСЕГДА закроет output
for num := range input {
output <- num * num
}
}
2. range автоматически заканчивается Когда канал закрывается:
for num := range input {
// Когда input закрывается, range завершается
}
3. Нельзя отправлять в закрытый канал
ch := make(chan int)
close(ch)
ch <- 1 // ❌ PANIC: send on closed channel
Сравнение с другими подходами
❌ Без defer
func squarePipeline(input <-chan int, output chan<- int) {
for num := range input {
output <- num * num
}
close(output) // может не выполниться при паник
}
✅ С defer
func squarePipeline(input <-chan int, output chan<- int) {
defer close(output) // ВСЕГДА выполнится
for num := range input {
output <- num * num
}
}
Производительность
// Буферизованные каналы лучше для производительности
input := make(chan int, 100) // буфер 100
output := make(chan int, 100) // буфер 100
// Избегает блокировок, если отправитель опережает приёмник
Ключевые выводы
- range на каналах — удобный способ итерировать до закрытия
- defer close() — гарантирует закрытие канала
- Pipelines — паттерн для обработки потоков данных
- Можно цеплять — несколько этапов в одной цепочке
- Не забывать закрывать — отправитель закрывает, приёмник завершает range
Eтот паттерн стандартный в Go для обработки конкурентных потоков данных.