Функция
#tip by Golangbot
👉 @juniorGolang | #tips
Join пакета errors конкатенирует список ошибок и возвращает ошибку, если хотя бы одна из переданных ошибок не nil.Join возвращает nil, если все переданные ошибки равны nil.#tip by Golangbot
Please open Telegram to view this post
VIEW IN TELEGRAM
👍12❤2🤔2
Вы, вероятно, слышали о gRPC, если еще не использовали её. Она особенно популярна для межсервисной связи благодаря своей эффективности, языковой независимости и встроенной поддержке таких вещей, как MTLS.
Выше представлен простой пример начала работы с gRPC от Matt Boyle.
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
👍12
%+v и %q вместо %v и %s.При отладке более четкий и детализированный вывод логов может быть очень полезен.
Возьмем типичную ситуацию, когда вы быстро проверяете состояние структуры с помощью
fmt.Println:fmt.Println(GameStats{30, 15, 5})Вывод:
{30 15 5}Здесь не сразу понятно, какое число относится к Wins, какое к Losses, а какое к Draws,
...если только вы не помните порядок полей в структуре.
Но, используя
%+v и %#v, которые являются вариациями %v, можно сделать вывод более информативным и удобочитаемым:%+v включает имена полей прямо в вывод, устраняя любую двусмысленность.%#v идет еще дальше, показывая полную структуру с пакетом и типом. Это может быть избыточным в повседневных сценариях, но полезно в сложных случаях."А как насчет %q?"
%q — это, по сути, способ обернуть строки в кавычки, тогда как %s просто выводит строку как есть.Это различие может быть полезным, особенно при работе с:
Просто посмотрите на логи, сгенерированные разными форматами, и обратите внимание, насколько понятнее становятся проблемы при использовании
%q."А есть ли недостатки?"
Стоит учитывать, что
%q не интерпретирует управляющие последовательности, такие как \t или \n, что может сделать строку менее читаемой.(Хотя это может быть полезно, поскольку позволяет увидеть строку в точности такой, какая она есть, сохраняя все специальные символы.)
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
👍15❤6🔥4
Управляем множеством горутин с errgroup
Когда у нас много горутин, бывает сложно обрабатывать ошибки и координировать их выполнение.
Ты, наверное, знаком с
В примере мы загружаем две страницы и ждем их завершения с
Три ключевых момента:
🔹 Для параллельного выполнения задач используй
🔹
🔹
При использовании
Как это работает внутри
1.
🔹
🔹
🔹 Семафорный
2.
🔹 Уменьшает счетчик активных горутин (если установлен лимит).
🔹 Сообщает
3. Ошибки обрабатываются централизованно.
🔹 С помощью
И да, горутины — это не всегда лучшее решение. Если задачи быстрые, последовательное выполнение может быть эффективнее
👉 @juniorGolang | #tip
Когда у нас много горутин, бывает сложно обрабатывать ошибки и координировать их выполнение.
Ты, наверное, знаком с
sync.WaitGroup, да? Но есть пакет errgroup, который упрощает эту задачу.В примере мы загружаем две страницы и ждем их завершения с
g.Wait().errgroup помогает управлять несколькими горутинами и обрабатывать ошибки, которые они могут вернуть.Три ключевых момента:
g.Go(), передавая в него функцию.g.Wait() ждет завершения всех горутин и возвращает первую возникшую ошибку (но не все сразу).errgroup хорошо работает с контекстом.При использовании
errgroup.WithContext() в случае ошибки контекст отменяется.Как это работает внутри
1.
Group использует несколько механизмов:sync.WaitGroup — чтобы дождаться завершения всех горутин.sync.Once — чтобы безопасно сохранить первую ошибку.chan — для ограничения числа одновременно работающих горутин (можно задать лимит через errg.SetLimit()).2.
errg.done() — вспомогательная функция, которая помечает горутину завершенной.WaitGroup, что горутина завершилась.3. Ошибки обрабатываются централизованно.
errOnce фиксируется только первая ошибка, которая затем приводит к остановке и возвращается через Wait().И да, горутины — это не всегда лучшее решение. Если задачи быстрые, последовательное выполнение может быть эффективнее
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11❤1
В Go 1.24 появился новый struct тег
Интересно то, что для него можно определить собственный метод
Это означает, что игнорироваться могут не только стандартные нулевые значения, но и любые, определенные по собственным условиям. Простор для магии и злоупотреблений.
👉 @juniorGolang | #tip
omitzero в encoding/json, который позволяет автоматически пропускать поля с нулевыми значениями.Интересно то, что для него можно определить собственный метод
IsZero().Это означает, что игнорироваться могут не только стандартные нулевые значения, но и любые, определенные по собственным условиям. Простор для магии и злоупотреблений.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍27🔥4
Трюк для сокрытия методов и предотвращения утверждений интерфейсов в Go.
Например, есть тип
Если передать экземпляр
Но если нужно использовать
При вызове
Хотя
Полезно в редких случаях (например,
👉 @juniorGolang | #tip
Например, есть тип
A с методом Hello(), который удовлетворяет интерфейсу Greeter.Если передать экземпляр
A в Greet(), интерфейсное утверждение успешно пройдет, и будет вызван Hello().Но если нужно использовать
A, не открывая его метод Hello(), можно скрыть его через встраивание.При вызове
Greet(ANoHello{A: A{}}) интерфейсное утверждение провалится и выведет: "g does not implement Greeter".Хотя
ANoHello встраивает A, содержащий метод Hello(), неэкспортируемое поле noHello имеет приоритет и скрывает его.Полезно в редких случаях (например,
os.File), но стоит знать этот трюк для лучшего понимания встраивания.Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
Буферизированные каналы как семафоры для ограничения выполнения горутин
Когда нам нужно управлять тем, сколько горутин могут одновременно получить доступ к ресурсу, использование семафора — надежный подход.
Мы можем создать семафор с помощью буферизированного канала, где размер канала определяет, сколько горутин может выполняться одновременно. Вот как это работает:
🔹 Горутин отправляет значение в канал, занимая одну ячейку.
🔹 После завершения своей задачи он извлекает значение, освобождая тем самым эту ячейку для другой горутины.
В этом примере:
🔹
🔹
Если нужен более аккуратный способ управления этим процессом, можно рассмотреть создание типа
Использование такого пользовательского типа Semaphore упрощает управление доступом к ресурсам в наших функциях.
Также существует реализация семафора в пакете golang.org/x/sync/semaphore, которая представляет собой взвешенный семафор.
Взвешенный семафор позволяет горутине занимать более одной ячейки, что полезно в сценариях, когда задачи требуют разного объема ресурсов.
Например, при управлении пулом подключений к базе данных, где некоторые операции могут одновременно потребовать несколько подключений.
👉 @juniorGolang | #tip
Когда нам нужно управлять тем, сколько горутин могут одновременно получить доступ к ресурсу, использование семафора — надежный подход.
Мы можем создать семафор с помощью буферизированного канала, где размер канала определяет, сколько горутин может выполняться одновременно. Вот как это работает:
В этом примере:
wg.Add(10) — мы подготавливаемся к завершению 10 горутин.make(chan struct{}, 3) — это создает семафор, позволяющий одновременно работать только 3 горутинам.Если нужен более аккуратный способ управления этим процессом, можно рассмотреть создание типа
Semaphore, который будет обрабатывать все действия, связанные с семафором.Использование такого пользовательского типа Semaphore упрощает управление доступом к ресурсам в наших функциях.
Также существует реализация семафора в пакете golang.org/x/sync/semaphore, которая представляет собой взвешенный семафор.
Взвешенный семафор позволяет горутине занимать более одной ячейки, что полезно в сценариях, когда задачи требуют разного объема ресурсов.
Например, при управлении пулом подключений к базе данных, где некоторые операции могут одновременно потребовать несколько подключений.
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11❤1
Настройка GOMAXPROCS для контейнерных сред (Kubernetes, Docker и др.)
⚠️ (Внимание: длинный и скучный материал)
Что такое GOMAXPROCS?
По умолчанию Go может запускать до 10 000 потоков одновременно, но фактическое количество потоков, выполняющихся параллельно, определяется важным параметром — GOMAXPROCS.
GOMAXPROCS устанавливает ограничение на количество потоков ОС, которые могут одновременно исполнять пользовательский Go-код (не просто конкуррентно, а именно параллельно).
Значение по умолчанию соответствует количеству логических ядер CPU, которое видит ОС (
Например, на моем MacOS это 8 ядер, а значит, Go будет одновременно выполнять до 8 потоков.
Запуск Go в контейнерах (Docker и Kubernetes)
В контейнеризированных средах, таких как Kubernetes, можно задать ограничения по CPU для каждого контейнера. Это означает, что контейнеру разрешено использовать только определённое количество процессорных ресурсов.
Пример:
🔹
🔹
Но проблема в том, что Go не видит эти ограничения. Он по-прежнему определяет количество доступных CPU на уровне хост-машины, а не контейнера.
(Хост-машина или нода может содержать несколько подов.)
Из-за этого Go-приложение может пытаться использовать больше процессорных ресурсов, чем ему выделено.
❓ "Но почему? Ведь чем больше CPU, тем лучше!"
Вот несколько причин, почему это может быть проблемой:
— Контекстные переключения – если потоков больше, чем ядер, ОС вынуждена чаще переключаться между ними.
— Неэффективное планирование – планировщик Go создаёт больше готовых к выполнению горутин, чем CPU может реально обработать, что приводит к избыточной конкуренции за процессорное время.
— Неэффективное выполнение CPU-bound задач – Go-программы часто CPU-bound, а значит, лучше работают, когда каждое ядро загружено ровно одним потоком без необходимости ожидания.
Если GOMAXPROCS больше, чем реально выделенные CPU-ресурсы, Go-рантайм планирует больше потоков, чем есть доступных ядер, что приводит к неэффективному выполнению.
✅ Решение
Если хочется автоматизировать настройку, можно использовать uber-go/automaxprocs. Эта библиотека корректирует GOMAXPROCS в соответствии с ограничением CPU контейнера.
(Если интересно, как работает uber-go/automaxprocs под капотом — просто скажите.)
Если вы понимаете спецификацию деплоя/пода, можно вручную задать переменную окружения
Но с точки зрения DevOps лучше вовсе избегать CPU-лимитов, а вместо них устанавливать только CPU requests (об этом подробнее ниже). Это касается не только Go-сервисов.
Задумайтесь об этом, если в вашем контейнере не задан лимит CPU.
Полезные ссылки:
— Почему не стоит использовать CPU-лимиты в Kubernetes
— Kubernetes CPU Limits и Go
👉 @juniorGolang | #tip
Что такое GOMAXPROCS?
По умолчанию Go может запускать до 10 000 потоков одновременно, но фактическое количество потоков, выполняющихся параллельно, определяется важным параметром — GOMAXPROCS.
GOMAXPROCS устанавливает ограничение на количество потоков ОС, которые могут одновременно исполнять пользовательский Go-код (не просто конкуррентно, а именно параллельно).
Значение по умолчанию соответствует количеству логических ядер CPU, которое видит ОС (
runtime.NumCPU()).Например, на моем MacOS это 8 ядер, а значит, Go будет одновременно выполнять до 8 потоков.
Запуск Go в контейнерах (Docker и Kubernetes)
В контейнеризированных средах, таких как Kubernetes, можно задать ограничения по CPU для каждого контейнера. Это означает, что контейнеру разрешено использовать только определённое количество процессорных ресурсов.
Пример:
limit: 250m = 1/4 ядраlimit: 1 = 1 полное ядроНо проблема в том, что Go не видит эти ограничения. Он по-прежнему определяет количество доступных CPU на уровне хост-машины, а не контейнера.
(Хост-машина или нода может содержать несколько подов.)
Из-за этого Go-приложение может пытаться использовать больше процессорных ресурсов, чем ему выделено.
Вот несколько причин, почему это может быть проблемой:
— Контекстные переключения – если потоков больше, чем ядер, ОС вынуждена чаще переключаться между ними.
— Неэффективное планирование – планировщик Go создаёт больше готовых к выполнению горутин, чем CPU может реально обработать, что приводит к избыточной конкуренции за процессорное время.
— Неэффективное выполнение CPU-bound задач – Go-программы часто CPU-bound, а значит, лучше работают, когда каждое ядро загружено ровно одним потоком без необходимости ожидания.
Если GOMAXPROCS больше, чем реально выделенные CPU-ресурсы, Go-рантайм планирует больше потоков, чем есть доступных ядер, что приводит к неэффективному выполнению.
Если хочется автоматизировать настройку, можно использовать uber-go/automaxprocs. Эта библиотека корректирует GOMAXPROCS в соответствии с ограничением CPU контейнера.
(Если интересно, как работает uber-go/automaxprocs под капотом — просто скажите.)
Если вы понимаете спецификацию деплоя/пода, можно вручную задать переменную окружения
GOMAXPROCS в соответствии с лимитом.Но с точки зрения DevOps лучше вовсе избегать CPU-лимитов, а вместо них устанавливать только CPU requests (об этом подробнее ниже). Это касается не только Go-сервисов.
Задумайтесь об этом, если в вашем контейнере не задан лимит CPU.
Полезные ссылки:
— Почему не стоит использовать CPU-лимиты в Kubernetes
— Kubernetes CPU Limits и Go
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11❤2
Оптимизируйте множественные вызовы с помощью singleflight
Допустим, у вас есть функция, которая получает данные по сети или выполняет ввод-вывод, и её выполнение занимает около 3 секунд:
Эта функция выдаёт новое значение каждые 10 секунд.
🔹 Если вызвать эту функцию 3 раза подряд, общее время ожидания составит примерно 9 секунд.
🔹 Если использовать 3 горутины, общее время ожидания может сократиться до 3 секунд, но функция всё равно будет вызвана 3 раза для получения одного и того же результата (~99%).
Здесь на помощь приходит пакет singleflight, который доступен по ссылке:
👉 golang.org/x/sync/singleflight.
Этот пакет позволяет выполнить функцию только один раз, независимо от того, сколько раз она была вызвана за эти 3 секунды, и возвращает один общий результат.
Это отлично подходит для оптимизации функций, работающих долго или потребляющих много ресурсов.
Как это работает?
1️⃣ Создаём объект
2️⃣ Передаём в метод
Метод
Параметр
Зачем нужен аргумент
Ключ (
Если поступает несколько запросов с одним и тем же ключом,
Пример работы: Go Playground
С этим подходом, если функция вызывается несколько раз одновременно, фактически выполняется только один её вызов. А результат этого единственного вызова разделяется между всеми ожидающими
👉 @juniorGolang | #tip
Допустим, у вас есть функция, которая получает данные по сети или выполняет ввод-вывод, и её выполнение занимает около 3 секунд:
Эта функция выдаёт новое значение каждые 10 секунд.
Здесь на помощь приходит пакет singleflight, который доступен по ссылке:
Этот пакет позволяет выполнить функцию только один раз, независимо от того, сколько раз она была вызвана за эти 3 секунды, и возвращает один общий результат.
Это отлично подходит для оптимизации функций, работающих долго или потребляющих много ресурсов.
Как это работает?
singleflight.Groupgroup.Do() функцию, которая выполняет дорогостоящие вычисления.Метод
group.Do() возвращает:(result any, err error, shared bool)
Параметр
shared показывает, был ли результат разделён между несколькими вызовами.Зачем нужен аргумент
key?Ключ (
key) — это идентификатор запроса.Если поступает несколько запросов с одним и тем же ключом,
singleflight понимает, что они запрашивают одно и то же.Пример работы: Go Playground
С этим подходом, если функция вызывается несколько раз одновременно, фактически выполняется только один её вызов. А результат этого единственного вызова разделяется между всеми ожидающими
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10❤2
Фильтрация без лишних аллокаций
При фильтрации слайсов в Go стандартный подход — создание нового слайса для отфильтрованных элементов.
Однако такой метод приводит к дополнительным аллокациям памяти.
Более эффективный способ — фильтровать «на месте», используя исходный массив слайса.
Как это работает:
•
• Добавляя
Таким образом, мы не выделяем новую память, а заполняем уже существующий массив.
Этот метод особенно полезен, когда:
•
• Критична производительность, особенно при работе с большими объемами данных.
👉 @juniorGolang | #tip
При фильтрации слайсов в Go стандартный подход — создание нового слайса для отфильтрованных элементов.
Однако такой метод приводит к дополнительным аллокациям памяти.
Более эффективный способ — фильтровать «на месте», используя исходный массив слайса.
Как это работает:
•
filtered := numbers[:0] создаёт новый слайс filtered, который ссылается на тот же массив, что и numbers, но имеет нулевую длину, сохраняя емкость numbers. • Добавляя
num в filtered, мы избегаем лишних аллокаций, так как просто изменяем содержимое numbers (или его базового массива).Таким образом, мы не выделяем новую память, а заполняем уже существующий массив.
Этот метод особенно полезен, когда:
•
numbers больше не нужен после фильтрации.• Критична производительность, особенно при работе с большими объемами данных.
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
👍16🔥7❤3👎1
Продолжайте работу с контекстами с помощью
Мы уже знаем, что при отмене родительского контекста отменяются и все его дочерние контексты, верно?
Но иногда это поведение нежелательно.
Есть случаи, когда нужно, чтобы определённые операции продолжались, даже если родительский контекст был отменён.
Представьте, что вы обрабатываете HTTP-запрос, и при его отмене (например, из-за таймаута или разрыва соединения с клиентом) всё ещё необходимо залогировать информацию о запросе и собрать метрики.
Это возможно, но новый контекст не содержит значений из исходного контекста события, которые важны, например, для логирования или сбора метрик.
Передача значений возможна только через дочерний контекст.
Возвращаясь к примеру с HTTP-запросом, решение следующее:
Кстати, эта функция появилась в Go 1.21.✌️
👉 @juniorGolang | #tip
context.WithoutCancel()Мы уже знаем, что при отмене родительского контекста отменяются и все его дочерние контексты, верно?
Но иногда это поведение нежелательно.
Есть случаи, когда нужно, чтобы определённые операции продолжались, даже если родительский контекст был отменён.
Представьте, что вы обрабатываете HTTP-запрос, и при его отмене (например, из-за таймаута или разрыва соединения с клиентом) всё ещё необходимо залогировать информацию о запросе и собрать метрики.
«Ха, просто создам новый контекст для этих операций»
Это возможно, но новый контекст не содержит значений из исходного контекста события, которые важны, например, для логирования или сбора метрик.
Передача значений возможна только через дочерний контекст.
Возвращаясь к примеру с HTTP-запросом, решение следующее:
context.WithoutCancel позволяет создать дочерний контекст, который не будет отменён при отмене родительского. Это обеспечивает завершение нужных операций даже после отмены запроса.Кстати, эта функция появилась в Go 1.21.
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11❤3
Избегайте использования
Когда вы работаете над проектами, где нужно генерировать ключи — например, для шифрования или создания уникальных идентификаторов — качество и безопасность этих ключей имеют большое значение.
Почему не
Пакет
Это означает, что если известен способ генерации чисел (seed), можно предсказать их значения.
Даже если использовать в качестве seed текущее время (например,
Почему
Пакет
Он спроектирован таким образом, чтобы быть непредсказуемым, используя источники случайности от операционной системы, которые гораздо сложнее предугадать.
👉 @juniorGolang | #tip
math/rand, вместо этого используйте crypto/rand для генерации ключейКогда вы работаете над проектами, где нужно генерировать ключи — например, для шифрования или создания уникальных идентификаторов — качество и безопасность этих ключей имеют большое значение.
Почему не
math/rand?Пакет
math/rand генерирует псевдослучайные числа.Это означает, что если известен способ генерации чисел (seed), можно предсказать их значения.
Даже если использовать в качестве seed текущее время (например,
time.Now().UnixNano()), уровень непредсказуемости (энтропии) остаётся низким, потому что время между запусками не сильно отличается.Почему
crypto/rand?Пакет
crypto/rand предоставляет способ генерации криптографически стойких случайных чисел.Он спроектирован таким образом, чтобы быть непредсказуемым, используя источники случайности от операционной системы, которые гораздо сложнее предугадать.
crypto/rand подходит для шифрования, аутентификации и других операций, чувствительных к безопасности.Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
👍20❤2
Получаем указатели проще с помощью дженериков
Небольшой совет для тех, кто пишет на Go и часто сталкивается с необходимостью получить указатель на значение.
Раньше вы, возможно, делали так (1 картинка)
Или пытались уместить всё в одну строчку, используя небольшой трюк (2 картинка)
Такой способ рабочий, но выглядит громоздко, особенно если приходится делать это часто для разных типов данных.
Теперь давайте посмотрим на более простой и современный способ с дженериками (3 картинка)
Эта небольшая функция позволяет создавать указатель для любого типа значения, не повторяя один и тот же код снова и снова.
Просто передайте своё значение в функцию
Такой подход делает код чище и избавляет от лишнего дублирования.
Больше не нужно писать отдельные функции или вручную обрабатывать указатели для каждого типа. Держите код простым и сосредоточьтесь на главном.
👉 @juniorGolang | #tip
Небольшой совет для тех, кто пишет на Go и часто сталкивается с необходимостью получить указатель на значение.
Раньше вы, возможно, делали так (1 картинка)
Или пытались уместить всё в одну строчку, используя небольшой трюк (2 картинка)
Такой способ рабочий, но выглядит громоздко, особенно если приходится делать это часто для разных типов данных.
Теперь давайте посмотрим на более простой и современный способ с дженериками (3 картинка)
Эта небольшая функция позволяет создавать указатель для любого типа значения, не повторяя один и тот же код снова и снова.
Просто передайте своё значение в функцию
Ptr, и вы получите нужный указатель (4 картинка)Такой подход делает код чище и избавляет от лишнего дублирования.
Больше не нужно писать отдельные функции или вручную обрабатывать указатели для каждого типа. Держите код простым и сосредоточьтесь на главном.
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
❤10👍6👎4🤔2