В продолжение темы про аллокации в куче. Если ваш высоконагруженный бэкенд создает тысячи временных объектов в секунду (например, буферы для сборки ответов на HTTP-запросы или парсинга JSON), сборщик мусора (GC) начинает задыхаться, сжигая драгоценное процессорное время.
Чтобы не выделять память каждый раз заново, в Go есть встроенный и мощный инструмент -
sync.Pool.Что это такое?
Это потокобезопасный механизм для хранения и переиспользования временных объектов.
Как это работает:
• Метод
Get() достает объект из пула. Если пул пуст, автоматически вызывается функция New, которая создает новый экземпляр.• Метод
Put() возвращает объект обратно в пул после того, как он стал не нужен.Где это реально полезно?
Идеальный кандидат для пула -
bytes.Buffer, массивы байт для чтения из сети или тяжелые структуры данных. Популярные пакеты вроде fmt, encoding/json или сверхбыстрый логгер zap от Uber активно используют sync.Pool под капотом именно для снижения нагрузки на GC.Важные нюансы (на которых часто обжигаются):
• Это не кэш. Сборщик мусора имеет полное право очистить
sync.Pool в любой момент (обычно во время очередного цикла сборки), удалив объекты, которые сейчас не используются. Не пытайтесь хранить там постоянные соединения с БД или пользовательские сессии.• Всегда сбрасывайте состояние. Перед тем как вернуть объект через
Put(), его нужно очистить (например, вызвать Reset()). Иначе следующая горутина, вызвавшая Get(), получит объект с чужими «грязными» данными, что приведет к плавающим и трудноотловимым багам.Пример правильного использования:
var bufPool = sync.Pool{
New: func() any {
return new(bytes.Buffer) // Вызывается только если пул пуст
},
}
func process() {
// Берем буфер из пула и кастуем к нужному типу
buf := bufPool.Get().(*bytes.Buffer)
// Гарантируем очистку и возврат буфера
defer func() {
buf.Reset() // Очищаем старые данные!
bufPool.Put(buf)
}()
// ... работаем с buf ...
}
#golang #backend #performance #память
📲 Мы в MAX
👉 @golang_lib