Go использует шаблон инкапсулированного возврата для освобождения памяти, занятой горутинами.

Go

оригинал: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