GOLANG中time.After釋放的問題
阿新 • • 發佈:2019-01-06
在謝大群裡看到有同學在討論time.After
洩漏的問題,就算時間到了也不會釋放,瞬間就驚呆了,忍不住做了試驗,結果發現應該沒有這麼的恐怖的,是有洩漏的風險不過不算是洩漏,先看API的說明:
// After waits for the duration to elapse and then sends the current time
// on the returned channel.
// It is equivalent to NewTimer(d).C.
// The underlying Timer is not recovered by the garbage collector
// until the timer fires. If efficiency is a concern, use NewTimer
// instead and call Timer.Stop if the timer is no longer needed.
func After(d Duration) <-chan Time {
return NewTimer(d).C
}
提到了一句The underlying Timer is not recovered by the garbage collector
,這句挺嚇人不會被GC回收,不過後面還有條件until the timer fires
fire
後是會被回收的,所謂fire
就是到時間了,寫個例子證明下壓壓驚:
package main
import "time"
func main() {
for {
<- time.After(10 * time.Nanosecond)
}
}
顯示記憶體穩定在5.3MB,CPU為161%,肯定被GC回收了的。當然如果放在goroutine也是沒有問題的,一樣會回收:
package main
import "time"
func main() {
for i := 0; i < 100; i++ {
go func(){
for {
<- time.After(10 * time.Nanosecond)
}
}()
}
time.Sleep(1 * time.Hour)
}
只是資源消耗會多一點,CPU為422%,記憶體佔用6.4MB。因此:
Remark: time.After(d)在d時間之後就會
fire
,然後被GC回收,不會造成資源洩漏的。
那麼API所說的If efficieny is a concern, user NewTimer instead and call Timer.Stop
是什麼意思呢?這是因為一般time.After
會在select中使用,如果另外的分支跑得更快,那麼timer是不會立馬釋放的(到期後才會釋放),比如這種:
select {
case time.After(3*time.Second):
return errTimeout
case packet := packetChannel:
// process packet.
}
如果packet非常多,那麼總是會走到下面的分支,上面的timer不會立刻釋放而是在3秒後才能釋放,和下面程式碼一樣:
package main
import "time"
func main() {
for {
select {
case <-time.After(3 * time.Second):
default:
}
}
}
這個時候,就相當於會堆積了3秒的timer沒有釋放而已,會不斷的新建和釋放timer,記憶體會穩定在2.8GB,這個當然就不是最好的了,可以主動釋放:
package main
import "time"
func main() {
for {
t := time.NewTimer(3*time.Second)
select {
case <- t.C:
default:
t.Stop()
}
}
}
這樣就不會佔用2.8GB記憶體了,只有5MB左右。因此,總結下這個After的說明:
- GC肯定會回收
time.After
的,就在d之後就回收。一般情況下讓系統自己回收就好了。 - 如果有效率問題,應該使用
Timer
在不需要時主動Stop。大部分時候都不用考慮這個問題的。
交作業。