2020-09-24 Обновление
Исправьте ошибку со статьей:
- удалить использование
time.Ticker
Метод исправляет ошибку, которая не соответствует логике тайм-аута select - Предыдущий метод использования go tool pprof для анализа использования памяти был неправильным, и теперь он был изменен.
предисловие
Привет всем, меня зовут Асонг, и сегодня я снова здесь. Вчера была опубликована статья:Научите мою сестру писать очередь сообщений вручную, один из кусков кода был найден внимательными читателями подверженным риску утечки памяти, что правда, я не заметил этого аспекта, а я, стремившийся к совершенству, тут же проверил и изменил это
bug
. я поставлю это сейчасbug
Поделитесь им, чтобы ваши друзья не наступили на яму в будущем.
Тестовый код выложен на github:GitHub.com/Assong2020/G…
Добро пожаловать звезда~~~
задний план
Позвольте мне сначала опубликовать сегмент кода, который вызовет утечку памяти, что можно лучше объяснить в соответствии с кодом:
func (b *BrokerImpl) broadcast(msg interface{}, subscribers []chan interface{}) {
count := len(subscribers)
concurrency := 1
switch {
case count > 1000:
concurrency = 3
case count > 100:
concurrency = 2
default:
concurrency = 1
}
pub := func(start int) {
for j := start; j < count; j += concurrency {
select {
case subscribers[j] <- msg:
case <-time.After(time.Millisecond * 5):
case <-b.exit:
return
}
}
}
for i := 0; i < concurrency; i++ {
go pub(i)
}
}
Прочитав этот код, вы знаете, где произошла утечка памяти? Позвольте мне сказать вам сначала, здесьtime.After(time.Millisecond * 5)
Утечки памяти будут происходить, не беспокойтесь о конкретных причинах, мы разберем их шаг за шагом.
проверять
Давайте напишем кусок кода для проверки, давайте сначала посмотрим на код:
package main
import (
"fmt"
"net/http"
_ "net/http/pprof"
"time"
)
/**
time.After oom 验证demo
*/
func main() {
ch := make(chan string,100)
go func() {
for {
ch <- "asong"
}
}()
go func() {
// 开启pprof,监听请求
ip := "127.0.0.1:6060"
if err := http.ListenAndServe(ip, nil); err != nil {
fmt.Printf("start pprof failed on %s\n", ip)
}
}()
for {
select {
case <-ch:
case <- time.After(time.Minute * 3):
}
}
}
Как мы можем проверить этот код? Глядя на код, вы, возможно, догадались, что это так.go tool pprof
, некоторые друзья могут не знать этот инструмент, тогда я кратко представлю основное использование, не вдаваясь в подробности, больше функций можно изучить самостоятельно.
вновь ввестиpprof
Раньше у нас был способ проверить, есть ли в этом коде утечка памяти, то есть использоватьtop
команда для просмотра занятости процессаcpu
ситуация, введитеtop
команда, мы увидимcpu
Парится, этот метод может определить утечку памяти, но не может определить, в какой части кода проблема, поэтому лучше использоватьpprof
инструмент для анализа, он может определить конкретный код проблемы.
доказательство введения
Обнаружение утечек goroutine будет использовать pprof.pprof — это инструмент производительности Go.Во время выполнения программы он может записывать текущую информацию о программе, которая может включать использование ЦП, использование памяти, операции goroutine и т. д. При настройке производительности или требуется позиционирование Когда есть ошибка, зарегистрированная информация очень важна. Есть много способов использовать pprof, и Go предлагает один из них:net/http/pprof
, с помощью нескольких простых строк команд вы можете запускать pprof, записывать текущую информацию и предоставлять веб-службы, которые могут получать рабочие данные через браузеры и командные строки.
Основное использование также очень простое, посмотрите на этот код:
package main
import (
"fmt"
"net/http"
_ "net/http/pprof"
)
func main() {
// 开启pprof,监听请求
ip := "127.0.0.1:6060"
if err := http.ListenAndServe(ip, nil); err != nil {
fmt.Printf("start pprof failed on %s\n", ip)
}
}
Он по-прежнему очень прост в использовании, так что давайте начнемgo tool pprof
. Теперь мы начинаем практиковаться, чтобы проиллюстрироватьpprof
использование.
процесс проверки
Сначала запустим мой тестовый код, затем откроем наш терминал и введем следующую команду:
$ go tool pprof -http=:8081 http://localhost:6060/debug/pprof/heap
Браузер откроется автоматически, см. изображение ниже:
Глядя на эту картинку, все взорвалось.time.Timer
Использование памяти ЦП увеличилось, теперь, когда проблема обнаружена, мы можем проанализировать ее ниже.
Анализ причин
Прежде чем анализировать конкретные причины, давайте взглянем на два таймера в ходу.ticker
а такжеtimer
, потому что я не знаю, как использовать эти два, я действительно не знаю конкретных причин.
тикер и таймер
Пакет time в Golang имеет два таймера: тикер и таймер. Оба могут реализовывать функции синхронизации, но у каждого есть свои сценарии использования.
Давайте посмотрим на их различия:
- Тикерный таймер означает, что он выполняется один раз через равные промежутки времени, обычно несколько раз.
- Таймер таймера означает, что он будет выполнен через определенный период времени. По умолчанию он выполняется только один раз. Если вы хотите выполнить его снова, вам нужно каждый раз вызывать метод time.Reset(). Эффект аналогичен к тикерному таймеру. В то же время вы также можете вызвать метод stop() для отмены таймера.
- Таймер-таймер имеет на один метод Reset() больше, чем таймер с тикером.Оба имеют метод Stop(), что означает остановку таймера.Нижний уровень вызывает функцию stopTimer().
причина
Выше мы представили два таймера перехода, теперь вернемся к нашей проблеме, наш код использует время.time.After
По сути, внутренний вызовtimer
таймер, согласноtimer
Характеристики таймера, конкретные причины очевидны.
Здесь наше время синхронизации установлено на 3 минуты, и каждый раз, когда цикл for выбирает, будет создаваться новый экземпляр таймера. Таймер будет активирован только через 3 минуты, но после активации он не имеет ссылки на выбор и очищается gc. Ключевым моментом здесь является то, чтоСборщик мусора не будет перерабатывать таймер, пока таймер не сработает., другими словами, заброшенная задача time.After все еще находится в куче времени, и она не будет очищена gc до истечения срока действия запланированной задачи, поэтому это является причиной утечки памяти. Новый объект таймера, создаваемый каждый раз, очищается сборщиком мусора за 3 минуты. Если мы изменим 3 минуты в приведенном выше коде на меньшее значение, оно улучшится, но все еще есть риски. Давайте использовать правильный , способ исправить эту ошибку.
исправлять ошибки
использоватьtimer
таймер
time.After
Хотя звонитtimer
таймер, но он не использовалtime.Reset()
Метод снова активирует таймер, поэтому каждый раз, когда это вновь созданный экземпляр, будет вызываться утечка памяти, мы добавляемtime.Reset
Каждый раз, когда таймер повторно активируется, проблема решается.
func (b *BrokerImpl) broadcast(msg interface{}, subscribers []chan interface{}) {
count := len(subscribers)
concurrency := 1
switch {
case count > 1000:
concurrency = 3
case count > 100:
concurrency = 2
default:
concurrency = 1
}
//采用Timer 而不是使用time.After 原因:time.After会产生内存泄漏 在计时器触发之前,垃圾回收器不会回收Timer
pub := func(start int) {
idleDuration := 5 * time.Millisecond
idleTimeout := time.NewTimer(idleDuration)
defer idleTimeout.Stop()
for j := start; j < count; j += concurrency {
if !idleTimeout.Stop(){
select {
case <- idleTimeout.C:
default:
}
}
idleTimeout.Reset(idleDuration)
select {
case subscribers[j] <- msg:
case <-idleTimeout.C:
case <-b.exit:
return
}
}
}
for i := 0; i < concurrency; i++ {
go pub(i)
}
}
Суммировать
Я не знаю, понимаете ли вы эту статью? Если вы не понимаете, вы можете скачать тестовый код и протестировать его самостоятельно, это будет более впечатляюще~~~
Эта статья в основном знакомит с идеей устранения неполадок,
go tool pprof
Этот инструмент очень важен.Если вы столкнулись с проблемами производительности и памяти gc, вы можете использовать инструмент golang pprof для устранения неполадок и анализа проблем. Те, кто не знает, все равно должны научиться этому~~Наконец, я хотел бы поблагодарить пользователя сети, который указал на проблему. Это заставило меня снова кое-что получить. Большое вам спасибо. Итак, давайте добиваться прогресса вместе. Если вы этого не сделаете, это не значит, что другие не будут , Друзья~~~
Напоследок дам небольшое пособие.Недавно читаю книгу [Паттерны проектирования архитектуры микросервисов], очень хорошая.Также собрал PDF.Кому нужно,могут скачать сами. Как получить: Подпишитесь на официальный аккаунт: [Golang DreamWorks], ответ за кулисами: [Microservice], вы можете его получить.
Я перевел документ GIN на китайский язык, который будет регулярно обновляться. Если он вам нужен, вы можете загрузить его, ответив на [gin] в фоновом режиме.
Я Асонг, обычный программист, дайте мне стать сильнее вместе. Я построил один самgolang
Группа для общения, нуждающиеся друзья добавляйте меняvx
, я тяну вас в группу. Приветствую всеобщее внимание, увидимся в следующем выпуске~~~
Рекомендуемые прошлые статьи:
-
Для подробного объяснения пакета Context достаточно этой статьи! ! !
-
Для начала работы с go-ElasticSearch достаточно прочитать эту статью (1)
-
Интервьюер: Вы использовали for-range в го? Можете ли вы объяснить причины этих вопросов?
-
На самом деле так просто научиться внедрению зависимостей проводов и запланированным задачам cron!
-
Я слышал, что ты не знаешь jwt и swagger-meal, я даже не ем его, и я приду с практическим проектом.
-
Освойте эти особенности языка го, ваш уровень повысится на N баллов (2)
-
Go реализует чат для нескольких человек, где вы можете поговорить обо всем, что захотите! ! !
-
босс: Этот ребенок не будет использовать библиотеку валидатора для проверки данных, откройте ее~~~