防踩坑知識點之 time.After

防踩坑知識點之 time.After

如何簡單實現(xiàn)請求調(diào)用的超時機制呢?有了 time.After 當(dāng)然就很簡單啦薪丁,代碼如下:

func AsyncCallWithTimeout1() {
    ctx, cancel := context.WithCancel(context.Background())
    go func(ctx context.Context) {
        defer cancel()
        // 模擬請求調(diào)用
        time.Sleep(200 * time.Millisecond)
    }(ctx)

    select {
    case <-ctx.Done():
        fmt.Println("call successfully!!!")
        return
    case <-time.After(time.Duration(3 * time.Second)):
        fmt.Println("timeout!!!")
        return
    }
}

測試該方法,輸出如下:

hello % go run ./hello.go 
call successfully!!!

和我們期待的一樣,成功了辆亏,沒有超時。
讓我們再測試下超時場景鳖目,修改模擬請求調(diào)用的超時時間為 4 秒扮叨,然后再測試該方法,輸出如下:

hello % go run ./hello.go
timeout!!!

果然發(fā)生了超時领迈,這就是我們想要的功能彻磁,如此簡單。
不過優(yōu)秀的程序猿都會仔細閱讀 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
}

憑借多年 google 翻譯 使用經(jīng)驗衷蜓,這里大概意思是:

Timer 不會被 GC 回收直到它被觸發(fā),如果需要考慮效率的話尘喝,Timer 不再被需要時磁浇,需要主動調(diào)用 Timer.Stop

臥槽朽褪,還好這次沒有草率置吓。如果有大量請求調(diào)用的場景,使用 time.After 會導(dǎo)致有大量的 Timer 對象被延遲釋放缔赠,造成大量內(nèi)存浪費衍锚。 原來正確的使用姿勢應(yīng)該是這個樣子的:

func AsyncCallWithTimeout2() {
    ctx, cancel := context.WithCancel(context.Background())

    go func() {
        defer cancel()
        // 模擬請求調(diào)用
        time.Sleep(200 * time.Millisecond)
    }()

    timer := time.NewTimer(3 * time.Second)
    defer timer.Stop()
    select {
    case <-ctx.Done():
        // fmt.Println("call successfully!!!")
        return
    case <-timer.C:
        // fmt.Println("timeout!!!")
        return
    }
}

本著優(yōu)秀程序猿是不會有 b???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????u???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????g??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????? 的精神,讓我們來驗證下這個姿勢還有沒有問題嗤堰。簡單的測試代碼如下:

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 1000000; i++ {
        wg.Add(1)
        go func() {
            AsyncCallWithTimeout1()
            // AsyncCallWithTimeout2()
            wg.Done()
        }()
    }
    wg.Wait()
    fmt.Println("all done!!!")

    for i := 0; i < 6; i++ {
        runtime.GC()
        time.Sleep(1 * time.Second)
    }
}

運行程序戴质,測試關(guān)鍵輸出如下:

hello % GODEBUG=gctrace=1 go run ./hello.go
# command-line-arguments
gc 1 @0.001s 12%: 0.006+2.2+0.017 ms clock, 0.075+1.5/4.1/0.75+0.21 ms cpu, 5->6->6 MB, 6 MB goal, 12 P
gc 2 @0.013s 5%: 0.004+2.2+0.017 ms clock, 0.053+0.85/4.1/2.8+0.21 ms cpu, 11->11->10 MB, 12 MB goal, 12 P
gc 3 @0.043s 3%: 0.004+3.6+0.005 ms clock, 0.058+0.15/9.7/9.7+0.065 ms cpu, 20->20->18 MB, 21 MB goal, 12 P
gc 1 @0.010s 6%: 0.10+23+0.33 ms clock, 1.2+14/5.9/0+3.9 ms cpu, 4->7->6 MB, 5 MB goal, 12 P
gc 2 @0.041s 15%: 0.14+7.0+0.14 ms clock, 1.6+42/20/0+1.7 ms cpu, 10->11->10 MB, 12 MB goal, 12 P
gc 3 @0.059s 20%: 0.28+23+0.20 ms clock, 3.4+77/28/0+2.5 ms cpu, 17->19->17 MB, 21 MB goal, 12 P
gc 4 @0.108s 22%: 0.053+32+0.23 ms clock, 0.64+121/49/0+2.8 ms cpu, 29->32->30 MB, 35 MB goal, 12 P
gc 5 @0.183s 25%: 0.076+27+0.20 ms clock, 0.91+196/77/0+2.4 ms cpu, 49->52->50 MB, 60 MB goal, 12 P
gc 6 @0.293s 28%: 0.11+47+0.093 ms clock, 1.3+376/140/0.086+1.1 ms cpu, 85->86->79 MB, 100 MB goal, 12 P
gc 7 @0.463s 30%: 0.071+72+0.13 ms clock, 0.85+584/214/0+1.6 ms cpu, 142->144->128 MB, 158 MB goal, 12 P
gc 8 @0.745s 29%: 0.36+122+1.2 ms clock, 4.3+767/337/0+14 ms cpu, 237->237->202 MB, 256 MB goal, 12 P
gc 9 @1.194s 29%: 0.13+189+0.99 ms clock, 1.6+1234/513/0+11 ms cpu, 388->391->312 MB, 405 MB goal, 12 P
all done!!!
gc 10 @2.050s 20%: 30+138+0.006 ms clock, 362+0/366/1011+0.081 ms cpu, 565->565->364 MB, 624 MB goal, 12 P (forced)
gc 11 @3.271s 14%: 0.082+104+0.041 ms clock, 0.98+0/295/853+0.49 ms cpu, 364->364->353 MB, 729 MB goal, 12 P (forced)
gc 12 @4.411s 11%: 0.024+78+0.29 ms clock, 0.29+0/206/499+3.5 ms cpu, 353->353->250 MB, 706 MB goal, 12 P (forced)
gc 13 @5.534s 9%: 0.035+31+0.006 ms clock, 0.42+0/61/308+0.082 ms cpu, 250->250->166 MB, 500 MB goal, 12 P (forced)
gc 14 @6.596s 7%: 0.026+30+0.005 ms clock, 0.31+0/58/292+0.062 ms cpu, 166->166->166 MB, 332 MB goal, 12 P (forced)
gc 15 @7.641s 6%: 0.027+31+0.006 ms clock, 0.33+0/79/283+0.076 ms cpu, 166->166->166 MB, 332 MB goal, 12 P (forced)

切換到 AsyncCallWithTimeout2 ,運行程序,測試關(guān)鍵輸出如下:

hello % GODEBUG=gctrace=1 go run ./hello.go
# command-line-arguments
gc 1 @0.002s 8%: 0.007+2.2+0.021 ms clock, 0.091+0.19/4.9/0.39+0.25 ms cpu, 4->7->6 MB, 5 MB goal, 12 P
gc 2 @0.014s 5%: 0.005+2.9+0.019 ms clock, 0.065+0/5.8/0.39+0.23 ms cpu, 10->11->10 MB, 12 MB goal, 12 P
gc 3 @0.040s 4%: 0.003+3.8+0.020 ms clock, 0.044+0.19/10/6.5+0.24 ms cpu, 18->19->17 MB, 20 MB goal, 12 P
gc 4 @0.082s 3%: 0.004+6.3+0.019 ms clock, 0.048+0/17/4.3+0.23 ms cpu, 31->33->28 MB, 34 MB goal, 12 P
gc 1 @0.008s 2%: 0.054+141+0.72 ms clock, 0.64+31/5.5/0+8.7 ms cpu, 4->25->24 MB, 5 MB goal, 12 P
gc 2 @0.179s 13%: 0.17+28+0.10 ms clock, 2.0+214/82/0+1.2 ms cpu, 38->41->37 MB, 48 MB goal, 12 P
gc 3 @0.263s 18%: 0.19+66+0.15 ms clock, 2.3+286/111/0+1.9 ms cpu, 62->68->64 MB, 75 MB goal, 12 P
gc 4 @0.412s 24%: 0.080+57+0.10 ms clock, 0.96+441/167/0+1.2 ms cpu, 109->110->100 MB, 128 MB goal, 12 P
gc 5 @0.603s 27%: 0.16+92+0.29 ms clock, 1.9+713/261/0+3.5 ms cpu, 180->184->154 MB, 200 MB goal, 12 P
gc 6 @0.957s 27%: 1.0+127+1.9 ms clock, 12+799/359/0+23 ms cpu, 291->292->203 MB, 308 MB goal, 12 P
gc 7 @1.417s 27%: 0.59+189+2.2 ms clock, 7.0+1176/511/0+26 ms cpu, 386->389->255 MB, 407 MB goal, 12 P
all done!!!
gc 8 @2.171s 20%: 29+92+0.005 ms clock, 351+0/146/314+0.070 ms cpu, 408->408->158 MB, 510 MB goal, 12 P (forced)
gc 9 @3.344s 14%: 0.025+34+0.006 ms clock, 0.30+0/69/297+0.078 ms cpu, 158->158->158 MB, 317 MB goal, 12 P (forced)
gc 10 @4.394s 11%: 0.025+29+0.005 ms clock, 0.30+0/59/288+0.061 ms cpu, 158->158->158 MB, 317 MB goal, 12 P (forced)
gc 11 @5.438s 9%: 0.024+32+0.004 ms clock, 0.29+0/72/285+0.058 ms cpu, 158->158->158 MB, 317 MB goal, 12 P (forced)
gc 12 @6.486s 7%: 0.024+33+0.008 ms clock, 0.29+0/67/310+0.10 ms cpu, 158->158->158 MB, 317 MB goal, 12 P (forced)
gc 13 @7.535s 6%: 0.020+30+0.007 ms clock, 0.24+0/60/293+0.089 ms cpu, 158->158->158 MB, 317 MB goal, 12 P (forced)

對比兩個的 GC 日志置森,可以發(fā)現(xiàn) AsyncCallWithTimeout2AsyncCallWithTimeout1 的內(nèi)存回收會及時很多斗埂,這也符合 time.After 文檔給的解釋。

所以結(jié)論是:

time.After 創(chuàng)建的 Timer 對象需要等到 Timer 被觸發(fā)時才能被 GC 回收釋放凫海,使用不當(dāng)會造成內(nèi)存浪費

開發(fā)環(huán)境:

go 1.14

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末呛凶,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子行贪,更是在濱河造成了極大的恐慌漾稀,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件建瘫,死亡現(xiàn)場離奇詭異崭捍,居然都是意外死亡,警方通過查閱死者的電腦和手機啰脚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進店門殷蛇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人橄浓,你說我怎么就攤上這事粒梦。” “怎么了荸实?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵匀们,是天一觀的道長。 經(jīng)常有香客問我准给,道長泄朴,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任露氮,我火速辦了婚禮祖灰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘畔规。我一直安慰自己局扶,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布油讯。 她就那樣靜靜地躺著详民,像睡著了一般。 火紅的嫁衣襯著肌膚如雪陌兑。 梳的紋絲不亂的頭發(fā)上沈跨,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天,我揣著相機與錄音兔综,去河邊找鬼饿凛。 笑死狞玛,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的涧窒。 我是一名探鬼主播心肪,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼纠吴!你這毒婦竟也來了硬鞍?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤戴已,失蹤者是張志新(化名)和其女友劉穎固该,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體糖儡,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡伐坏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了握联。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片桦沉。...
    茶點故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖金闽,靈堂內(nèi)的尸體忽然破棺而出纯露,到底是詐尸還是另有隱情,我是刑警寧澤呐矾,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布苔埋,位于F島的核電站懦砂,受9級特大地震影響蜒犯,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜荞膘,卻給世界環(huán)境...
    茶點故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一罚随、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧羽资,春花似錦淘菩、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至腹暖,卻和暖如春汇在,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背脏答。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工糕殉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留亩鬼,地道東北人。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓阿蝶,卻偏偏與公主長得像雳锋,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子羡洁,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,871評論 2 354

推薦閱讀更多精彩內(nèi)容