оригинал:medium.com/@evaluation21/gos-…
вопрос
Когда у нас есть фоновые горутины, которые создают объект через его внутренний конструктор, мы хотим, чтобы объект был своевременно собран мусором даже после того, как горутины не были вовремя закрыты. Это невозможно, потому что горутины, работающие в фоновом режиме, всегда будут работать и указывать на этот объект.
Решение
Мы инкапсулируем возвращенный объект, а затем используем финализатор этого объекта, чтобы закрыть фоновые горутины.
взять каштан
Предположим, теперь у нас есть статический клиент для Go.go-statsd-client, который создаетBufferedSenderследующим образом:
func NewBufferedSender(addr string, flushInterval time.Duration, flushBytes int) (Sender, error) {
simpleSender, err := NewSimpleSender(addr)
if err != nil {
return nil, err
}
sender := &BufferedSender{
flushBytes: flushBytes,
flushInterval: flushInterval,
sender: simpleSender,
buffer: senderPool.Get(),
shutdown: make(chan chan error),
}
sender.Start()
return sender, nil
}
Start
Дублирование метода создает горутину для периодического обновления.BufferedSender.
func (s *BufferedSender) Start() {
// write lock to start running
s.runmx.Lock()
defer s.runmx.Unlock()
if s.running {
return
}
s.running = true
s.bufs = make(chan *bytes.Buffer, 32)
go s.run()
}
Теперь мы создаем и используем этоBufferedSenderпосмотреть, что происходит
func Process() {
x := statsd.NewBufferedSender("localhost:2125", time.Second, 1024)
x.Inc("stat", 1, .1)
}
самое началоmain gorotinuesуказывает наx, но когда мы выходимProcess
когдаBufferedSenderвсе еще работает, потому чтоStart
Запущенные горунтины не останавливаются.
Мы эквивалентны утечкеBufferedSenderпамяти, потому что мы забыли позвонитьCloseчтобы закрыть его.
решение
Взгляните на библиотеку кэширования Go.go-cache. Ты заметишьCache
На самом деле это просто пакет.
type Cache struct {
*cache
// If this is confusing, see the comment at the bottom of New()
}
type cache struct {
defaultExpiration time.Duration
items map[string]Item
mu sync.RWMutex
onEvicted func(string, interface{})
janitor *janitor
}
когда тыnewОдинCacheобъект, он вернет прокси, прокси указывает на инкапсулированный объектcache
, вместо возвращаемого объектаCache
func New(defaultExpiration, cleanupInterval time.Duration) *Cache {
items := make(map[string]Item)
return newCacheWithJanitor(defaultExpiration, cleanupInterval, items)
}
func newCacheWithJanitor(de time.Duration, ci time.Duration, m map[string]Item) *Cache {
c := newCache(de, m)
// This trick ensures that the janitor goroutine (which--granted it
// was enabled--is running DeleteExpired on c forever) does not keep
// the returned C object from being garbage collected. When it is
// garbage collected, the finalizer stops the janitor goroutine, after
// which c can be collected.
C := &Cache{c}
if ci > 0 {
runJanitor(c, ci)
runtime.SetFinalizer(C, stopJanitor)
}
return C
}
func runJanitor(c *cache, ci time.Duration) {
j := &janitor{
Interval: ci,
stop: make(chan bool),
}
c.janitor = j
go j.Run(c)
}
Обратитесь к нему и измените наш код на
func Process() {
x := cache.New(time.Second, time.Minute)
}
Очень важное отличие здесьCache
может быть собран мусор, даже еслиcache
Объекты не могут быть переработаны. Мы устанавливаем GC актор SetFinalizer вcache
начальство.stopJanitor
Функция уведомит горунтины, работающие в фоновом режиме, о прекращении работы.
runtime.SetFinalizer(C, stopJanitor)
...
...
func stopJanitor(c *Cache) {
c.janitor.stop <- true
}
Когда фоновые горутины остановлены, больше не на что указыватьcache
.
Потом будет сборка мусора.
когда его использовать
Это действительно зависит от того, что думают пользователи вашей библиотеки, хотят ли они иметь возможность явно создавать и закрывать фоновые процессы. Идтиhttp.Serveхороший пример. Обратите внимание, что это неfunc NewHTTPServer() *http.Server
, вместо этого используется объект, и пользователь может явно запустить (или остановить) сервер, когда он будет готов.
Основываясь на этой наилучшей реализации, если вы хотите управлять закрытием фоновых процессов, вам все равно следует открытьClose
Эта функция позволяет пользователю закрывать фоновые горутины для достижения цели освобождения памяти. Но если вы думаете, пусть пользователь позвонитClose
Это более хлопотно, вы можете добавить пакет финализатора, чтобы гарантировать, что внутренний запуск и созданная вами горунтина могут быть правильно переработаны в конце независимо от того, вызывается он или нет.Close