Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Назначение каналов в Go
Определение
Каналы (Channels) — это примитив для синхронизации и безопасной передачи данных между горутинами. Они позволяют горутинам безопасно обмениваться значениями без явных блокировок.
Это основной инструмент для параллельного программирования в Go.
Основное назначение каналов
1. Синхронизация между горутинами
func main() {
// Создаём небуферизированный канал
done := make(chan bool)
// Запускаем горутину
go func() {
fmt.Println("Работаю...")
time.Sleep(2 * time.Second)
fmt.Println("Готово!")
done <- true // Сигнал завершения
}()
// Блокируемся до сигнала
<-done
fmt.Println("Главная функция завершена")
}
// Вывод:
// Работаю...
// (2 секунды ожидания)
// Готово!
// Главная функция завершена
2. Передача данных между горутинами
func ProcessNumbers(numbers []int) {
// Канал для передачи результатов
results := make(chan int)
// Запускаем горутины для обработки
for _, num := range numbers {
go func(n int) {
// Обработка
result := n * n
results <- result // Отправляем результат
}(num)
}
// Собираем результаты
for i := 0; i < len(numbers); i++ {
fmt.Println("Результат:", <-results)
}
}
3. Работа с timeouts
func FetchWithTimeout(url string) (string, error) {
// Канал для результата
resultChan := make(chan string, 1)
errChan := make(chan error, 1)
go func() {
// Долгий HTTP запрос
resp, err := http.Get(url)
if err != nil {
errChan <- err
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
resultChan <- string(body)
}()
// Ждём результата или timeout
select {
case result := <-resultChan:
return result, nil
case err := <-errChan:
return "", err
case <-time.After(5 * time.Second):
return "", fmt.Errorf("timeout")
}
}
4. Работа с select (мультиплексирование)
func ProcessMultipleSources() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(1 * time.Second)
ch1 <- "Данные от источника 1"
}()
go func() {
time.Sleep(2 * time.Second)
ch2 <- "Данные от источника 2"
}()
// Обработка данных из разных источников
for i := 0; i < 2; i++ {
select {
case data := <-ch1:
fmt.Println("Получено из 1:", data)
case data := <-ch2:
fmt.Println("Получено из 2:", data)
}
}
}
Типы каналов
Небуферизированные каналы
// Синхронные, требуют одновременного отправителя и получателя
ch := make(chan int)
go func() {
ch <- 42 // БЛОКИРУЕТСЯ до чтения
}()
value := <-ch // БЛОКИРУЕТСЯ до записи
fmt.Println(value)
Буферизированные каналы
// Асинхронные, отправитель не блокируется при заполнении буфера
ch := make(chan int, 3) // Буфер на 3 элемента
ch <- 1
ch <- 2
ch <- 3
// ch <- 4 // Заблокировалась бы
fmt.Println(<-ch) // 1
fmt.Println(<-ch) // 2
fmt.Println(<-ch) // 3
Практические примеры
Пример 1: Worker Pool
func WorkerPool(numWorkers int, jobs <-chan int, results chan<- int) {
var wg sync.WaitGroup
// Создаём worker горутины
for w := 0; w < numWorkers; w++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
for job := range jobs {
// Обработка работы
result := job * 2
results <- result
}
}(w)
}
go func() {
wg.Wait()
close(results) // Закрыть результаты когда все готово
}()
}
func main() {
jobs := make(chan int, 5)
results := make(chan int)
go WorkerPool(3, jobs, results)
// Отправляем работы
for i := 1; i <= 10; i++ {
jobs <- i
}
close(jobs)
// Собираем результаты
for result := range results {
fmt.Println(result)
}
}
Пример 2: Pipeline (конвейер обработки)
func main() {
// Источник
nums := make(chan int)
go func() {
for i := 1; i <= 5; i++ {
nums <- i
}
close(nums)
}()
// Этап 1: умножение на 2
doubled := make(chan int)
go func() {
for n := range nums {
doubled <- n * 2
}
close(doubled)
}()
// Этап 2: добавление 10
result := make(chan int)
go func() {
for n := range doubled {
result <- n + 10
}
close(result)
}()
// Получаем результаты
for n := range result {
fmt.Println(n) // 12, 14, 16, 18, 20
}
}
Пример 3: Отмена операций (Context + Channels)
func FetchDataWithCancel(ctx context.Context, id int) (string, error) {
resultChan := make(chan string, 1)
go func() {
// Долгая операция
time.Sleep(3 * time.Second)
resultChan <- fmt.Sprintf("Data for ID %d", id)
}()
select {
case result := <-resultChan:
return result, nil
case <-ctx.Done():
return "", ctx.Err()
}
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
result, err := FetchDataWithCancel(ctx, 123)
if err != nil {
fmt.Println("Error:", err) // Timeout
} else {
fmt.Println(result)
}
}
Пример 4: Fan-Out / Fan-In
// Fan-out: распределение работы между горутинами
func FanOut(work []int, numWorkers int) []<-chan int {
channels := make([]<-chan int, numWorkers)
itemsPerWorker := len(work) / numWorkers
for i := 0; i < numWorkers; i++ {
ch := make(chan int)
start := i * itemsPerWorker
end := start + itemsPerWorker
if i == numWorkers-1 {
end = len(work)
}
go func(items []int) {
for _, item := range items {
ch <- item * item
}
close(ch)
}(work[start:end])
channels[i] = ch
}
return channels
}
// Fan-in: объединение результатов
func FanIn(channels ...<-chan int) <-chan int {
var wg sync.WaitGroup
out := make(chan int)
output := func(c <-chan int) {
for n := range c {
out <- n
}
wg.Done()
}
wg.Add(len(channels))
for _, c := range channels {
go output(c)
}
go func() {
wg.Wait()
close(out)
}()
return out
}
Закрытие каналов
ch := make(chan int)
// Только отправитель может закрыть канал!
close(ch)
// Проверка закрытия
value, ok := <-ch
if !ok {
fmt.Println("Канал закрыт")
}
// Итерация пока открыт
for value := range ch {
fmt.Println(value)
}
Best Practices
✅ Правильно:
// Передавайте каналы как параметры
func Worker(in <-chan int, out chan<- int) {
for n := range in {
out <- process(n)
}
}
// Закрывайте каналы когда готовы
close(results)
// Используйте буферизированные для простоты
ch := make(chan int, 10)
❌ Неправильно:
// Не читайте из закрытого канала
<-closedChan // panic!
// Не закрывайте если могут писать
close(ch) // panic if someone tries to send
// Не создавайте каналы если простая переменная подойдёт
ch := make(chan int)
ch <- x
y := <-ch
// Просто используйте y = x
Выводы
Каналы в Go нужны для:
- Синхронизации горутин без mutexes
- Безопасной передачи данных между горутинами
- Реализации паттернов (pipelines, workers, fan-out/fan-in)
- Обработки таймаутов (select с time.After)
- Создания event-driven архитектур
Каналы — это фундамент параллельного программирования в Go!