GOLANG中time.After釋放的問題

winlin發表於2017-07-29

在謝大群裡看到有同學在討論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的說明:

  1. GC肯定會回收time.After的,就在d之後就回收。一般情況下讓系統自己回收就好了。
  2. 如果有效率問題,應該使用Timer在不需要時主動Stop。大部分時候都不用考慮這個問題的。

交作業。

相關文章