Ответ
Конкурентность в Go — это мощный инструмент, который следует использовать для решения определенных классов задач, где он дает наибольший выигрыш.
Основные случаи применения:
Операции, связанные с вводом-выводом (I/O-bound задачи):
- Описание: Это задачи, где программа большую часть времени ожидает ответа от внешнего ресурса. Горутины позволяют эффективно использовать процессорное время, пока одна из них заблокирована ожиданием.
- Примеры:
- Сетевые запросы (HTTP API, gRPC).
- Работа с базами данных.
- Чтение/запись файлов на диск.
- Взаимодействие с брокерами сообщений (Kafka, RabbitMQ).
Распараллеливание вычислений (CPU-bound задачи):
- Описание: Это задачи, требующие интенсивных вычислений. Для получения реального ускорения необходимо наличие нескольких ядер процессора (
GOMAXPROCS > 1
). Горутины позволяют разбить одну большую задачу на несколько меньших и выполнить их параллельно. - Примеры:
- Обработка изображений или видео.
- Сложные математические расчеты.
- Криптографические операции.
- Описание: Это задачи, требующие интенсивных вычислений. Для получения реального ускорения необходимо наличие нескольких ядер процессора (
Обработка событий и асинхронных потоков данных:
- Описание: Когда приложению нужно одновременно реагировать на множество независимых событий.
- Примеры:
- Веб-сервер, обрабатывающий тысячи одновременных подключений.
- Приложения, работающие с WebSocket.
- Потребители (consumers) из очередей сообщений.
Пример: конкурентная загрузка сайтов
func fetchURLs(urls []string) {
var wg sync.WaitGroup // Ожидаем завершения всех горутин
ch := make(chan string) // Канал для результатов
for _, url := range urls {
wg.Add(1) // Увеличиваем счетчик горутин
go func(u string) {
defer wg.Done() // Уменьшаем счетчик по завершении
resp, err := http.Get(u)
if err != nil {
ch <- fmt.Sprintf("Ошибка при загрузке %s: %v", u, err)
return
}
defer resp.Body.Close()
ch <- fmt.Sprintf("Сайт %s доступен, статус: %s", u, resp.Status)
}(url)
}
// Горутина для закрытия канала, когда все загрузки завершатся
go func() {
wg.Wait()
close(ch)
}()
// Читаем все результаты из канала
for result := range ch {
fmt.Println(result)
}
}
Когда не стоит использовать:
- Для простых, последовательных операций, где накладные расходы на создание горутин и синхронизацию могут превысить выгоду.
- Без понимания потенциальных проблем: состояний гонки (
race conditions
), взаимных блокировок (deadlocks
) и утечек горутин.