Открытый канал ID 72625731109378

Библиотека Go (Golang) разработчика

Полезные материалы по всему, что может быть полезно Golang разработчику.

Каналов в рубрике
1 733
Место этого канала
№ 42
Открыть рубрику →
Каналов в рубрике
17 376
Место этого канала
№ 83
Открыть рубрику →
Открыть в MAX

Публикации за период

По реакциям

Поиск и вся лента — после бесплатной регистрации. Свои даты — по подписке; владелец канала видит их бесплатно. Сортировка по реакциям — по подписке. Зарегистрироваться

Загрузка…

🧠 Go Memory Model: Почему ваша горутина слепая (и что такое Happens-Before)

Напишем простейший код. Одна горутина меняет флаг, другая ждет этого изменения:

var done bool
var msg string

func setup() {
msg = "hello, world"
done = true
}

func main() {
go setup()

// Ждем, пока setup() закончит работу
for !done {
}

fmt.Println(msg)
}

Джун посмотрит на этот код и скажет: "Ну, он выведет hello, world".
Сеньор посмотрит на этот код и скажет: "Этот код может уйти в бесконечный цикл, может вывести пустую строку, а может работать нормально. Это лотерея".

Добро пожаловать в жестокий мир Go Memory Model.

Иллюзия мгновенной памяти

Мы привыкли думать, что память - это единый монолит. Записал переменную - она тут же стала доступна всем. Но в реальности код выполняется на современных многоядерных процессорах. У каждого ядра есть свой сверхбыстрый локальный кэш (L1/L2).

Когда горутина setup пишет done = true, процессор может положить это значение в свой локальный кэш и не сбрасывать его в общую оперативную память (RAM) еще миллисекунды. А горутина main, работающая на другом ядре, будет бесконечно читать старое значение done == false из своего собственного кэша.

Но и это не всё! Компилятор Go невероятно умен. Он смотрит на цикл for !done {} и думает: "Внутри этого цикла переменная done не меняется. Зачем мне читать её из памяти на каждой итерации? Прочитаю один раз, сохраню в регистр процессора и буду проверять его". Итог - вечный цикл (Infinite Loop).

Святой Грааль: Happens-Before (Происходит-До)

Чтобы навести порядок в этом хаосе, создатели Go написали формальный документ - Модель Памяти (Memory Model). В её основе лежит концепция Happens-Before.

Если событие A происходит до события B, то Модель Памяти гарантирует, что горутина, выполняющая B, абсолютно точно увидит изменения, сделанные в A.

Если между действиями нет связи Happens-Before, компилятор и процессор имеют право переупорядочивать инструкции как им вздумается, а кэши могут не синхронизироваться.

Как создать связь Happens-Before?

Вам нужны примитивы синхронизации. Они работают как жесткие барьеры памяти (Memory Barriers), заставляя процессоры сбросить кэши, а компилятор - прекратить умничать с перестановкой строк кода.

✅ 1. Каналы (Channels)
Отправка данных в канал происходит до завершения чтения из этого канала в другой горутине.

c := make(chan struct{})
go func() {
msg = "hello"
c <- struct{}{} // Happens-Before
}()
<-c // Ждем
fmt.Println(msg) // 100% увидит "hello"


✅ 2. Мьютексы (Mutexes)
Снятие блокировки mu.Unlock() происходит до успешного захвата mu.Lock() другой горутиной.

✅ 3. Атомики (sync/atomic)
Начиная с Go 1.19, в Модель Памяти официально добавили пакет atomic. Атомарные операции теперь гарантированно создают связь Happens-Before.

✅ 4. Жизненный цикл горутины
Создание горутины (go func()) происходит до начала выполнения её кода. А вот завершение горутины не имеет гарантий! (Именно поэтому нам нужен sync.WaitGroup, чтобы дождаться её финала).

🔥 Senior Tip: Не пытайтесь обмануть систему

Существует соблазн написать код, который работает "без блокировок", опираясь на задержки: time.Sleep(time.Millisecond). Это называется data race (состояние гонки). Даже если на вашей машине с процессором Intel код работает идеально, завтра его скомпилируют под ARM-архитектуру (Apple Silicon или AWS Graviton), у которой гораздо более слабая модель памяти процессора, и ваш код рассыплется в пыль.

Всегда используйте флаг -race во время локальной разработки и CI: go test -race ./.... Он ловит отсутствие связей Happens-Before с хирургической точностью.

#golang #underhood #architecture #performance #memorymodel

👉 @golang_lib
Просмотры: 213
Перейти в пост Открыть в MAX Сообщить о неточности
🛑 Graceful Shutdown: Хватит обрывать запросы при деплое

Знакомая ситуация: вы катите новую версию микросервиса в Kubernetes, и в этот момент графики в Grafana на пару секунд краснеют от 502/503 ошибок. А потом всё снова хорошо.

Что произошло? Оркестратор убил старый под, а ваш Go-сервис моментально схлопнулся, оборвав на полуслове десятки текущих HTTP-запросов, сломав пару транзакций к базе и бросив недокачанные файлы.

Как это работает в здоровом проде?
Когда Kubernetes хочет обновить под, он не стреляет ему в голову сразу. Он посылает вежливый сигнал SIGTERM и дает время (обычно 30 секунд), чтобы собрать вещи.
Мы должны перехватить этот сигнал и сказать нашему HTTP-серверу: "Новые запросы не принимай, а те, что уже в работе - спокойно доделывай".

Как это написать:
Забудьте про ручное создание make(chan os.Signal). Используйте signal.NotifyContext.

func main() {
// 1. Создаем контекст, который отменится при SIGINT (Ctrl+C) или SIGTERM (Docker/K8s)
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()

srv := &http.Server{Addr: ":8080", Handler: mux}

// 2. Запускаем сервер в отдельной горутине!
go func() {
// http.ErrServerClosed возвращается при успешном Shutdown, это не ошибка
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatalf("Критическая ошибка сервера: %v", err)
}
}()

// 3. Блокируемся и ждем сигнала от ОС
<-ctx.Done()
log.Println("Получен сигнал завершения. Тушим свет...")

// 4. Создаем контекст с таймаутом для Shutdown
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

// 5. Плавно останавливаем сервер (ждет завершения текущих хендлеров)
if err := srv.Shutdown(shutdownCtx); err != nil {
log.Printf("Сервер не смог остановиться плавно: %v", err)
}

// 6. Здесь закрываем коннекты к БД, RabbitMQ, сбрасываем кэши в Redis
// db.Close()

log.Println("Сервер успешно завершил работу.")
}

🔥 Нюансы для Senior-ов:
• Обязательно ставьте таймаут на Shutdown(). Если клиент с медленным интернетом качает от вас гигабайтный файл, srv.Shutdown будет ждать его вечно. В итоге K8s устанет ждать свои 30 секунд и жестко убьет под через SIGKILL (kill -9). Контролируйте таймаут сами (Шаг 4).

• Порядок важен. Сначала останавливаем HTTP-сервер (чтобы перестали прилетать новые задачи), и только потом закрываем пул соединений с БД. Если сделать наоборот, текущие HTTP-запросы упадут с ошибкой sql: database is closed.

#golang #architecture #kubernetes #bestpractices #devops

👉 @golang_lib
Просмотры: 472
Перейти в пост Открыть в MAX Сообщить о неточности
🚀 Подборка полезных IT каналов в Max


Системное администрирование, DevOps 📌

https://max.ru/i_odmin Все для системного администратора
https://max.ru/bash_srv Bash Советы
https://max.ru/sysadminof Книги для админов, полезные материалы
https://max.ru/i_odmin_book Библиотека Системного Администратора
https://max.ru/i_devops DevOps: Пишем о Docker, Kubernetes и др.
https://max.ru/tipsysdmin Типичный Сисадмин
https://max.ru/channel_win_sysadmin Системный Администратор Windows
https://max.ru/channel_linux_admin Linux: Системный администратор
https://max.ru/channel_linuxmod Linux
https://max.ru/channel_i_linux Системный администратор
https://max.ru/channel_devopslib DevOps, SRE, Sysadmin
https://max.ru/channel_devops_star DevOps Star (Звезда Девопса)

Excel лайфхак 📌
https://t.me/Excel_lifehack Excel лайфхак

Английский с нуля 🇬🇧
https://max.ru/UchuEnglish

1C разработка 📌
https://max.ru/odin1c_rus Cтатьи, курсы, советы, шаблоны кода 1С
https://max.ru/channel_DevLab1C 1С:Предприятие 8

Программирование C++📌
https://max.ru/cpp_lib Библиотека C/C++ разработчика
https://max.ru/channel_cpp_geek C++ geek

Программирование Go📌
https://max.ru/golang_lib Библиотека Go (Golang) разработчика

Программирование React📌
https://max.ru/react_lib React

Программирование Rust📌
https://max.ru/channel_rust_lib

Программирование Python 📌
https://max.ru/python_of Python академия.
https://max.ru/BookPython Библиотека Python разработчика

Java разработка 📌
https://max.ru/bookjava Библиотека Java разработчика
https://max.ru/channel_java_geek Java Geek

GitHub Сообщество 📌
https://max.ru/githublib Интересное из GitHub

Базы данных (Data Base) 📌
https://max.ru/database_info Все про базы данных

Фронтенд разработка 📌
https://max.ru/frontend_1 Подборки для frontend разработчиков

Библиотеки 📌
https://max.ru/programmist_of Книги по программированию
https://max.ru/proglb Библиотека программиста
https://max.ru/bfbook Книги для программистов

Программирование 📌
https://max.ru/bookflow Лекции, видеоуроки, доклады с IT конференций
https://max.ru/itmozg Программисты, дизайнеры, новости из мира IT
https://max.ru/php_lib Библиотека PHP программиста 👨🏼‍💻👩‍💻

Шутки программистов 📌
https://max.ru/itumor Шутки программистов

Защита, взлом, безопасность 📌
https://max.ru/thehaking Канал о кибербезопасности
https://max.ru/xakkep_1 Хакер Free

Книги, статьи для дизайнеров 📌
https://max.ru/odesigners Статьи, книги для дизайнеров

Математика 📌
https://max.ru/Pomatematike Канал по математике
https://max.ru/phismat_1 Обучающие видео, книги по Физике и Математике

Вакансии в IT📌
https://max.ru/progjob
https://max.ru/channel_rabotait

Мир технологий 📌
https://max.ru/mir_teh Канал для любознательных

Городские📌
https://max.ru/piterspb_78 Свежие новости Санкт-Петербурга
https://max.ru/mockva_life Свежие новости Москвы
https://max.ru/piterspb Питер Новости: Санкт-Петербург / СПБ / ДТП
https://max.ru/channel_krasnodar_novosty Краснодар Новости
https://max.ru/channel_novosibirsk_novosti Новосибирск
https://max.ru/channel_samara_novosti Новости Самары
https://max.ru/channel_ekaterinburg_novosti Новости Екатеринбурга
https://max.ru/channel_kazan_novosti Новости Казани
https://max.ru/channel_omsk_novosti Новости Омска
https://max.ru/channel_moskva_24 Москва 24
Просмотры: 453
Перейти в пост Открыть в MAX Сообщить о неточности
🐳 Идеальный Dockerfile для Go. Худеем с 800 МБ до 10 МБ

Знакомая картина: джун приносит микросервис, который делает пару запросов к БД, а Docker-образ весит под гигабайт. Почему? Потому что в FROM гордо красуется golang:latest.

Вместе с вашим кодом в продакшен уезжает весь тулчейн компилятора, операционная система, куча утилит (шелл, curl) и, как бонус, пачка потенциальных уязвимостей.

Прелесть Go в том, что он компилирует всё в один независимый бинарный файл. Нам не нужен Go в рантайме! Наш путь - Multi-stage builds (Многоэтапная сборка).

Вот "золотой стандарт" Dockerfile, который работает в 99% случаев:

# === Этап 1: Сборка (Builder) ===
FROM golang:1.22-alpine AS builder

WORKDIR /app

# Шаг 1: Кэшируем зависимости
# Этот слой пересоберется ТОЛЬКО если изменились go.mod или go.sum
COPY go.mod go.sum ./
RUN go mod download

# Шаг 2: Копируем код
COPY . .

# Шаг 3: Собираем бинарник
# CGO_ENABLED=0 - критически важно для статической линковки
# -ldflags="-w -s" - вырезаем отладочную информацию
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o /app/bin/service ./cmd/server/main.go

# === Этап 2: Финальный образ (Runner) ===
# Используем distroless вместо alpine или scratch
FROM gcr.io/distroless/static-debian12:nonroot

WORKDIR /app

# Забираем только готовый бинарник из первого этапа
COPY --from=builder /app/bin/service .

# Контейнер будет работать от непривилегированного пользователя
USER nonroot:nonroot

EXPOSE 8080
ENTRYPOINT ["/app/service"]


Разбираем нюансы под капотом (Senior Edition):

• Зачем нужен CGO_ENABLED=0? По умолчанию Go может динамически линковаться с системными C-библиотеками (например, libc). Если вы соберете такой бинарник и положите в пустой образ, он упадет при старте. Отключая CGO, мы принудительно делаем статическую линковку - всё необходимое запекается прямо внутрь файла.

• Флаги -ldflags="-w -s": Они вырезают DWARF-таблицы и символы отладки. Это бесплатно срезает ~20-30% веса бинарника. Не переживайте, трейсы паник (stack traces) всё ещё будут показывать номера строк, так что для прода это безопасно.

• Почему distroless/static, а не scratch или alpine? * В scratch (абсолютно пустом образе) нет корневых сертификатов (ca-certificates). Как только ваш сервис попытается сделать внешний HTTPS-запрос, он упадет. Там также нет таймзон (tzdata).
- В alpine есть sh (шелл) и пакетный менеджер apk - это векторы атаки для хакеров, если они найдут RCE в вашем коде.
- Google Distroless содержит только сертификаты, таймзоны и юзера nonroot. Идеальный баланс микроскопического размера (~2 МБ) и безопасности.

У кого образы до сих пор весят больше 50 МБ - срочно переписывать!

#golang #docker #devops #architecture #security

👉 @golang_lib
Просмотры: 507
Перейти в пост Открыть в MAX Сообщить о неточности
🧠 Go Memory Model: Почему ваша горутина слепая (и что такое Happens-Before)

Напишем простейший код. Одна горутина меняет флаг, другая ждет этого изменения:

var done bool
var msg string

func setup() {
msg = "hello, world"
done = true
}

func main() {
go setup()

// Ждем, пока setup() закончит работу
for !done {
}

fmt.Println(msg)
}


Джун посмотрит на этот код и скажет: "Ну, он выведет `hello, world`".
Сеньор посмотрит на этот код и скажет: "Этот код может уйти в бесконечный цикл, может вывести пустую строку, а может работать нормально. Это лотерея".

Добро пожаловать в жестокий мир Go Memory Model.

Иллюзия мгновенной памяти

Мы привыкли думать, что память - это единый монолит. Записал переменную — она тут же стала доступна всем. Но в реальности код выполняется на современных многоядерных процессорах. У каждого ядра есть свой сверхбыстрый локальный кэш (L1/L2).

Когда горутина setup пишет done = true, процессор может положить это значение в свой локальный кэш и не сбрасывать его в общую оперативную память (RAM) еще миллисекунды. А горутина main, работающая на другом ядре, будет бесконечно читать старое значение done == false из своего собственного кэша.

Но и это не всё! Компилятор Go невероятно умен. Он смотрит на цикл for !done {} и думает: "Внутри этого цикла переменная done не меняется. Зачем мне читать её из памяти на каждой итерации? Прочитаю один раз, сохраню в регистр процессора и буду проверять его". Итог - вечный цикл (Infinite Loop).

Святой Грааль: Happens-Before (Происходит-До)

Чтобы навести порядок в этом хаосе, создатели Go написали формальный документ - Модель Памяти (Memory Model). В её основе лежит концепция Happens-Before.

> Если событие A происходит до события B, то Модель Памяти гарантирует, что горутина, выполняющая B, абсолютно точно увидит изменения, сделанные в A.

Если между действиями нет связи Happens-Before, компилятор и процессор имеют право переупорядочивать инструкции как им вздумается, а кэши могут не синхронизироваться.

Как создать связь Happens-Before?

Вам нужны примитивы синхронизации. Они работают как жесткие барьеры памяти (Memory Barriers), заставляя процессоры сбросить кэши, а компилятор - прекратить умничать с перестановкой строк кода.

✅ 1. Каналы (Channels)
Отправка данных в канал происходит до завершения чтения из этого канала в другой горутине.

c := make(chan struct{})
go func() {
msg = "hello"
c <- struct{}{} // Happens-Before
}()
<-c // Ждем
fmt.Println(msg) // 100% увидит "hello"


✅ 2. Мьютексы (Mutexes)
Снятие блокировки mu.Unlock() происходит до успешного захвата mu.Lock() другой горутиной.

✅ 3. Атомики (sync/atomic)
Начиная с Go 1.19, в Модель Памяти официально добавили пакет atomic. Атомарные операции теперь гарантированно создают связь Happens-Before.

✅ 4. Жизненный цикл горутины
Создание горутины (go func()) происходит до начала выполнения её кода. А вот завершение горутины не имеет гарантий! (Именно поэтому нам нужен sync.WaitGroup, чтобы дождаться её финала).

🔥 Senior Tip: Не пытайтесь обмануть систему

Существует соблазн написать код, который работает "без блокировок", опираясь на задержки: time.Sleep(time.Millisecond). Это называется data race (состояние гонки). Даже если на вашей машине с процессором Intel код работает идеально, завтра его скомпилируют под ARM-архитектуру (Apple Silicon или AWS Graviton), у которой гораздо более слабая модель памяти процессора, и ваш код рассыплется в пыль.

Всегда используйте флаг -race во время локальной разработки и CI: go test -race ./.... Он ловит отсутствие связей Happens-Before с хирургической точностью.

#golang #underhood #architecture #performance #memorymodel

👉 @golang_lib
Просмотры: 533
Перейти в пост Открыть в MAX Сообщить о неточности
📢 sync.Cond: Как разбудить 10 000 горутин одним вызовом (и не сломать планировщик)

Представьте задачу: у вас запущено 10 000 горутин-воркеров, которые должны начать обработку данных строго одновременно, но только после того, как в память загрузится тяжелый кэш или конфигурация.

Как передать им сигнал "на старт"?

Вариант 1: Каналы.
Если мы отправим сигнал в канал ch <- struct{}{}, проснется только одна горутина. Чтобы разбудить 10 000, придется писать цикл. Долго и неэффективно.
Можно сделать close(ch) - закрытие канала мгновенно разбудит все горутины, читающие из него. Но канал можно закрыть только один раз. Что если нам нужно подавать такой массовый сигнал каждые 5 минут при обновлении кэша?

Здесь на сцену выходит sync.Cond (Condition Variable) - один из самых недооцененных примитивов синхронизации.

Это рупор, через который одна горутина может крикнуть толпе спящих горутин: "Событие произошло, просыпайтесь!"

Как это работает

У sync.Cond есть три главных метода:

* Wait() - усыпить текущую горутину до получения сигнала.
* Signal() - разбудить одну случайную спящую горутину.
* Broadcast() - разбудить все спящие горутины разом.

Чтобы sync.Cond работал, ему обязательно нужен мьютекс (sync.Mutex или sync.RWMutex), который защищает условие.

✅ Как это выглядит в коде:

type AppState struct {
mu sync.Mutex
cond *sync.Cond
configLoad bool
}

func NewAppState() *AppState {
a := &AppState{}
// Cond всегда связывается с мьютексом
a.cond = sync.NewCond(&a.mu)
return a
}

func (a *AppState) Worker(id int) {
a.mu.Lock() // Обязательно лочим мьютекс перед Wait!

// 🤡 Джун напишет тут 'if'. Сеньор пишет 'for'. Почему — ниже.
for !a.configLoad {
// Wait атомарно снимает лок с a.mu и усыпляет горутину.
// Когда она проснется, Wait атомарно вернет лок на место.
a.cond.Wait()
}

a.mu.Unlock() // Разлочили и пошли работать
fmt.Printf("Воркер %d начал работу!\n", id)
}

func (a *AppState) UpdateConfig() {
a.mu.Lock()
a.configLoad = true
a.mu.Unlock()

// БУМ! Будим все тысячи спящих горутин одним вызовом
a.cond.Broadcast()
}


🔥 Senior Tip: Почему Wait() всегда пишут в цикле for?

Главная ловушка sync.Cond заключается в том, что вызов Wait() нельзя использовать внутри if !condition { cond.Wait() }.
Спецификация POSIX-систем и рантайм Go допускают Spurious Wakeups (Ложные пробуждения). Горутина может проснуться просто так, даже если никто не вызывал Broadcast.
Кроме того, между моментом, когда горутина проснулась, и моментом, когда она захватила мьютекс для проверки, другая горутина могла успеть поменять состояние configLoad обратно на false.
Цикл for гарантирует, что проснувшаяся горутина обязательно еще раз проверит условие, прежде чем пойти дальше.

Когда sync.Cond НЕ нужен?
Если событие происходит только один раз за время жизни приложения (например, Graceful Shutdown) - не тащите sync.Cond. Обычного закрытия канала close(done) или использования context.Context хватит за глаза. sync.Cond блистает именно в сценариях многоразовых массовых оповещений.

#golang #concurrency #sync #underhood #architecture

👉 @golang_lib
Просмотры: 557
Перейти в пост Открыть в MAX Сообщить о неточности
🔎 pprof: Как найти функцию, которая жрет 80% CPU

Сервис на проде внезапно упирается в полку по процессору.

Что делает новичок? Сидит и смотрит в исходники взглядом гипнотизера, пытаясь угадать: "Ну, наверное, это регулярка тормозит". Или обкладывает весь код вызовами time.Now() в начале и time.Since() в конце каждой функции.

Сеньор открывает терминал, пишет одну команду и через 30 секунд получает точное имя функции, номер строки и процент украденного CPU. Наш инструмент pprof.

Он встроен прямо в стандартную библиотеку Go. Работает по принципу семплирования: раз в 10 миллисекунд рантайм Go "замирает", смотрит на стеки всех запущенных горутин и записывает: "Так, в эту микросекунду процессор выполнял функцию json.Unmarshal.

Давайте проведем вскрытие за 4 шага.

Шаг 1. Включаем «жучка» в коде

Всё, что нужно сделать в вашем сервисе - импортировать один пакет со знаком подчеркивания:

package main

import (
"net/http"
_ "net/http/pprof" // <-- Подключаем магию
)

func main() {
// ... ваша основная бизнес-логика ...

// Вешаем pprof на отдельный внутренний порт!
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
}


Нюанс для Senior-ов: Магия _ "net/http/pprof" работает только с дефолтным http.DefaultServeMux. Если вы используете chi, gin или кастомный http.NewServeMux(), роуты профилировщика нужно регистрировать в ваш роутер руками (они лежат в пакете net/http/pprof как обычные хендлеры).

Шаг 2. Натравливаем профилировщик

Сервис крутится под нагрузкой. Открываем терминал на своей рабочей машине и говорим Go: *"Послушай этот сервер 30 секунд и собери мне статистику"*:

go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30

Терминал подумает полминуты и превратится в интерактивную консоль (pprof).

Шаг 3. Магия двух колонок

Внутри консоли пишем команду top10:

Showing nodes accounting for 2.45s, 88.1% of 2.78s total
flat flat% sum% cum cum%
1.82s 65.5% 65.5% 1.82s 65.5% regexp.(*bitState).reset
0.34s 12.2% 77.7% 0.45s 16.2% encoding/json.Unmarshal
0.15s 5.4% 83.1% 2.50s 89.9% main.processOrder
Главная ловушка новичка - смотреть на колонку cum.

• flat - сколько времени процессор провел исключительно внутри тела этой функции.
• cum (cumulative) - сколько времени процессор провел в этой функции + во всех функциях, которые она вызвала внутри себя.

Пример: У функции main.processOrder показатель cum равен 89.9%, но flat всего 5.4%. Это значит, что сама она почти ничего не считает, она просто вызвала тяжелую регулярку (regexp.reset, у которой flat аж 65.5%). Лечить нужно регулярку, а не processOrder!

Шаг 4. Визуальный экстаз (Flame Graph)

Смотреть в ASCII-таблицы в 2026 году больно. Выходим из консоли (Ctrl+D) и запускаем ту же команду, но добавив флаг -http:

go tool pprof -http=:8080 http://localhost:6060/debug/pprof/profile?seconds=30

В браузере мгновенно откроется шикарный веб-интерфейс. Переключаем вид сверху в режим Flame Graph (Огненный граф):

1. Перед вами "столбы". Чем шире столб по горизонтали - тем больше CPU сожрала эта функция. 2. Кликаем мышкой на самый широкий красный столб.
2. Переключаемся во вкладку Source.
3. Видим наш исходный код, где подсвечена конкретная строчка var re = regexp.MustCompile(...), компилирующая регулярку внутри цикла в хендлере. Прод спасен.


🔥 Senior Warning: Кровавая цена дефолта

Никогда не выставляйте порт 6060 наружу в интернет!

По адресу /debug/pprof/goroutine?debug=2 любой школьник без авторизации скачает полный текстовый дамп всех запущенных горутин. В их стеках будут лежать ваши пароли от БД в сыром виде, Bearer-токены пользователей и приватные ключи.

В продакшене pprof обязан висеть строго на localhost, куда разработчик заходит через kubectl port-forward или SSH-туннель.

👉 @golang_lib
Просмотры: 489
Перейти в пост Открыть в MAX Сообщить о неточности
🗺️ sync.Map: Почему эта «серебряная пуля» иногда пробивает дно производительности

Как только новичок ловит свою первую панику fatal error: concurrent map writes, он тут же гуглит проблему, находит sync.Map и радостно заменяет все свои словари на него. Казалось бы, идеальное решение из коробки.

Но если вы откроете исходники стандартной библиотеки Go, то увидите, что сами разработчики языка используют sync.Map крайне редко. В 95% случаев обычный map + sync.RWMutex порвет sync.Map по производительности.

Давайте разберем, как эта штука устроена под капотом и почему она может тормозить ваш сервис.

Архитектура двух корзин
Внутри sync.Map лежат не одна, а две мапы:

1. read: Доступна для чтения вообще без блокировок.
2. dirty: Защищена классическим мьютексом.

Когда вы делаете Load(key), Go сначала бежит в read. Если ключ найден - супер, мы прочитали его lock-free.
Но если ключа там нет, Go вынужден брать мьютекс и искать его в dirty мапе. Это называется cache miss. Если промахов становится слишком много, sync.Map принимает тяжелое решение: он берет всю dirty мапу и копирует её в read.

Когда sync.Map - это катастрофа?

• Частая запись новых ключей: Каждый новый ключ попадает в dirty. Это вызывает постоянные промахи при чтении, мьютекс постоянно лочится, а затем происходит дорогое копирование всей мапы. Вы получаете двойной удар по CPU.
• Удаление и перезапись: Стандартный RWMutex справится с этим гораздо эффективнее.

Когда sync.Map - это шедевр? (Два идеальных юзкейса)
Официальная документация выделяет ровно два сценария, где этот тип оправдан:

1. Append-only кэши: Когда записи добавляются один раз и живут вечно (например, кэш скомпилированных регулярных выражений или конфигураций).
2. Disjoint keys (Разделенные ключи): Когда у вас есть 1000 горутин, и каждая читает/пишет строго в свой собственный ключ, никогда не пересекаясь с соседями.

🔥 Senior Tip: Sharded Map (Шардирование)
Что делать, если у вас кэш на 5 миллионов записей, куда постоянно идут конкурентные чтение и запись? RWMutex станет узким местом (будет блокировать всю мапу целиком).
В высоконагруженном проде используют Шардирование. Вы создаете слайс из 256 обычных мап, у каждой из которых свой маленький мьютекс. Хэш от ключа определяет, в какую именно мапу пойдет запрос. Это снижает конкуренцию за лок в 256 раз! (Готовые решения: orcaman/concurrent-map или alphadose/haxmap).

#golang #concurrency #architecture #performance #underhood

👉 @golang_lib
Просмотры: 539
Перейти в пост Открыть в MAX Сообщить о неточности
⚡️ 40 000 000 ₽ ждут, пока ты дочитаешь этот пост

Если ты пишешь код, рисуешь интерфейсы или анализируешь данные до трёх ночи — у нас для тебя новость. Стартовала регистрация на конкурс «Лидеры цифровой трансформации», и призовой фонд там не шуточный —40 000 000 ₽ 🔥

🥇 1 место — 1 000 000 ₽
🥈 2 место — 600 000 ₽
🥉 3 место — 400 000 ₽

Что ещё:
💼 20 живых задач Правительства Москвы и бизнеса: Positive Technologies, «Газпромбанк.Тех», билайн бизнес, Yandex Cloud, VK Tech, РСХБ, «Фалькон Тех», ГК «Геоскан», ИТ Школа Ростелекома, ОЭЗ «Алабуга».
🧠 14 дней на разработку вашего решения
🪩 в конце — молодежный технологический фестиваль с награждением, нетворкингом, интерактивными стендами и крутой программой

Кто может участвовать?
👉🏻 Специалисты от 16 лет в любом цифровом направлении — ИИ-инженеры, разработчики, специалисты по обработке и анализу данных, аналитики, проджект-менеджеры, дизайнеры….

Собирай свою команду профи и принимай вызов!
Кто знает, возможно, эти призовые деньги ждут именно тебя 😉

📲 Регистрация тут:
https://i.moscow/lct?utm_source=sp&utm_medium=social&utm_campaign=golang_lib/?erid=2VtzqwmH2SP
До 14 сентября — не пропусти!

Рекламный пост

Просмотры: 510
Перейти в пост Открыть в MAX Сообщить о неточности
☢️ Пакет unsafe: Взламываем систему типов Go

Пакет unsafe - это легальный способ сказать компилятору: «Отключи ремни безопасности, я беру управление на себя». С его помощью можно обходить строгую типизацию и напрямую управлять памятью, но любая ошибка приведет к моментальному падению сервиса (Segmentation Fault).

Проблема стандартной конвертации
В Go строки (string) иммутабельны (неизменяемы), а срезы байт ([]byte) можно менять. Когда вы пишете s := string(bytes), Go вынужден копировать всю память. Это делается для того, чтобы вы не смогли изменить исходный массив и случайно поменять символы в строке. В высоконагруженных местах (парсинг JSON, HTTP-роутеры) эти копирования плодят мусор и съедают CPU.

Zero-allocation конвертация (Go 1.20+)
Долгое время разработчики занимались черной магией, жонглируя внутренними структурами StringHeader и SliceHeader через указатели. С версии Go 1.20 в язык добавили элегантные функции unsafe.String и unsafe.Slice.

import "unsafe"

// Из []byte в string без копирования памяти
func BytesToString(b []byte) string {
if len(b) == 0 {
return ""
}
// Говорим Go: "Смотри на этот кусок памяти как на строку"
return unsafe.String(&b[0], len(b))
}

// Из string в []byte без аллокаций
func StringToBytes(s string) []byte {
if len(s) == 0 {
return nil
}
return unsafe.Slice(unsafe.StringData(s), len(s))
}


В чем смертельная опасность?
Вы обманули компилятор, но физика осталась прежней. Если вы превратили string в []byte с помощью unsafe, а затем попытались изменить этот срез (например, b[0] = 'X'), ваша программа мгновенно упадет. Строки часто хранятся в защищенной от записи секции памяти (read-only). Попытка записи туда вызывает панику на уровне операционной системы, которую невозможно перехватить через recover().

Когда это реально нужно?

• Написание сверхбыстрых логгеров (популярные библиотеки вроде zap или zerolog используют это под капотом).
• Парсинг потоковых сетевых протоколов, где вы просто читаете миллионы пакетов без их модификации.
• Только тогда, когда pprof явно показал, что функция runtime.slicebytetostring является главным узким местом приложения.

GC и устаревший API
Если вы поддерживаете легаси-код на старых версиях Go, где используется reflect.StringHeader с полем Data uintptr - срочно переписывайте. Garbage Collector в Go не видит связи между uintptr и реальным объектом в памяти. При неудачном тайминге GC мог очистить память, пока вы с ней работали. Новые методы unsafe.String сохраняют ссылочную целостность для сборщика мусора.

#golang #unsafe #performance #underhood #hardcore

👉 @golang_lib
Просмотры: 377
Перейти в пост Открыть в MAX Сообщить о неточности
Вся лента — после бесплатной регистрации
ВКонтакте Телеграм Одноклассники
Избранное в datool.ru

Следите за каналом в личном кабинете

Зарегистрируйтесь в datool.ru, чтобы добавить канал «Библиотека Go (Golang) разработчика» в избранное и отслеживать его показатели.

Зарегистрироваться
Для владельцев каналов

Подтвердите свой канал

Войдите или зарегистрируйтесь в datool.ru, затем добавьте нашего бота в канал по инструкции в личном кабинете. После проверки владения замок исчезнет и вам станет доступно управление каналом.

Войти или зарегистрироваться

Сообщить о неточности

Опишите, что нужно исправить. Если есть источник, добавьте ссылку.

Вы покидаете datool.ru

Ссылка ведёт на внешний сайт. datool.ru не отвечает за его содержимое — проверьте адрес, прежде чем вводить пароли или платёжные данные.

Переход через 10 с

Перейти