Ответ
Для организации чтения из одного канала несколькими горутинами (паттерн "Competing Consumers" или "Fan-out"), можно запустить несколько горутин, которые читают из этого общего канала в цикле for range
.
Go runtime автоматически и конкурентно распределит значения из канала между всеми читающими горутинами. Каждое отправленное в канал значение будет получено только одной горутиной.
Пример:
package main
import (
"fmt"
"sync"
"time"
)
func main() {
jobs := make(chan int, 100)
var wg sync.WaitGroup
// Запускаем 3 воркера (горутины) для чтения
for i := 1; i <= 3; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
for j := range jobs {
fmt.Printf("Воркер %d обработал задачу %dn", id, j)
time.Sleep(time.Millisecond * 500) // Имитация работы
}
}(i)
}
// Отправляем 10 задач в канал
for j := 1; j <= 10; j++ {
jobs <- j
}
close(jobs) // Важно закрыть канал, чтобы циклы for range завершились
wg.Wait() // Ожидаем завершения всех воркеров
fmt.Println("Все задачи обработаны.")
}
Ключевые аспекты:
- Конкурентное чтение: Операция чтения из канала (
<-ch
) является блокирующей и атомарной. Когда несколько горутин ожидают на одном канале, только одна из них получит следующее значение. - Закрытие канала: Канал обязательно нужно закрывать (
close(ch)
) после того, как все данные были в него отправлены. Это является сигналом для цикловfor range
в горутинах, что данных больше не будет, и они могут безопасно завершиться. В противном случае произойдетdeadlock
. - Недетерминированное распределение: Нет гарантии, что данные будут распределяться между горутинами поочередно или в каком-либо предсказуемом порядке. Распределение зависит от внутреннего планировщика Go.
- Синхронизация: Часто вместе с этим паттерном используется
sync.WaitGroup
, чтобы главная горутина могла дождаться завершения работы всех дочерних горутин.