Вопрос проверяет понимание применения буферизированных каналов в Go для реализации паттернов синхронизации и ограничения параллелизма.
Буферизированные каналы — это каналы с фиксированной ёмкостью, которые позволяют отправлять данные без блокировки, пока буфер не заполнен. Они идеально подходят для паттернов, где нужно контролировать параллелизм или сглаживать нагрузку.
Используется для ограничения количества операций в единицу времени. Буферизированный канал выступает в роли счётчика токенов: горутина отправляет токен перед выполнением, а после завершения — читает его. Если буфер полон, отправка блокируется, пока не освободится место.
tokens := make(chan struct{}, 3) // максимум 3 одновременных запроса
for i := 0; i < 10; i++ {
go func(id int) {
tokens <- struct{}{} // захват токена
defer func() { <-tokens }() // освобождение
fmt.Println("Request", id)
time.Sleep(100 * time.Millisecond)
}(i)
}Семафор ограничивает доступ к общему ресурсу. Буферизированный канал с ёмкостью N работает как семафор: горутина отправляет значение, чтобы "захватить" слот, и читает, чтобы "освободить". Это гарантирует, что не более N горутин одновременно работают с ресурсом.
sem := make(chan struct{}, 2) // семафор на 2 слота
for _, task := range tasks {
go func(t Task) {
sem <- struct{}{}
defer func() { <-sem }()
process(t)
}(task)
}Пул воркеров создаёт фиксированное число горутин, которые читают задачи из общего канала. Буферизированный канал для задач позволяет накапливать запросы, пока воркеры заняты. Это предотвращает потерю данных и сглаживает пиковую нагрузку.
jobs := make(chan Job, 100) // буфер на 100 задач
for w := 0; w < 5; w++ {
go worker(jobs)
}
for _, job := range jobList {
jobs <- job
}
close(jobs)Вывод: Буферизированные каналы применяются везде, где нужно контролировать параллелизм: от веб-серверов (лимит запросов) до обработки данных (пул воркеров). Они делают код безопасным и предсказуемым, избегая перегрузки системы.