goroutine與panic不得不說的故事

來源:互聯網
上載者:User

我之前對golang還瞭解的極其膚淺的時候,就已經對goroutine如雷貫耳了,我相信很多同學跟我一樣,會以為在go代碼中,goroutine的身影隨處可見,事實上並不是這樣。

這兩天參與了金融部門的一個小項目,把一個老系統中的小模組從php代碼重構成golang。因為負責重構的同事之前只有php經驗,所以派我和另外一個同事去幫忙。今早總監過來看看進度,無意中看了眼My Code,立刻給我指出了一個嚴重bug,讓我發現了一個知識盲點,我覺得值得分享一下。

過程

昨天下午寫了一個grpc介面,根據user_id從資料庫查詢一張user_config表,拿到一個city_ids欄位,是個city_id組成的字串,然後split處理後查city表取城市資料,大概過程類似這樣:

func GetCities(userID int64) ([]*cityData, error) {    var (        strCityIDs string         CityIDs []string        ret []*cityData    )    strCityIDs, _ = userConfig.GetCityIDs(userID) //從user_config表查詢city_id欄位    CityIDs = strings.Split(strCityIDs, sep) //處理成id數組    err = city.Find(CityIDs, &ret) //從city表查出資料    return ret, err}

說白了就是個has_many關係。因為city表幾乎不會變化,早上來了公司,我覺得可以加個緩衝,所以改成了:

func GetCities(userID int64) ([]*cityData, error) {    var (        strCityIDs string         CityIDs []string        ret []*cityData    )    strCityIDs, _ = userConfig.GetCityIDs(userID) //從user_config表查詢city_id欄位    err := cache.Get(prefix+strCityIDs, &ret) //先從緩衝拿資料    if err == nil {        return ret, nil    }    CityIDs = strings.Split(strCityIDs, sep) //處理成id數組    err = city.Find(CityIDs, &ret) //從city表查出資料    if err == nil {        ok := cache.Set(prefix+strCityIDs, &ret, 12*time.Hour) //存入緩衝        if !ok {            doNothing()        }    }    return ret, err}

改完後“靈機”一動,想起自己幾乎沒在公司項目中看到過go關鍵字的出現,自己也基本沒在生產中實際用過goroutine,於是把cache.Set改成了go cache.Set。我覺得存入緩衝成功與否並不影響主流程(即便失敗其實我也什麼都不做),所以完全可以交給協程去做,而且這樣主goroutine可以返回的更快。
這時總監過來了。
聊了兩句,突然指著代碼跟我說:“這裡不對,不能用協程!”
我:“為啥啊?”
總監:“因為協程裡面發生panic會讓整個進程crash。”
我更加迷惑了:“但是我在middleware裡加了recover啊,會抓到panic的。”
middleware代碼:

func (*Interceptor) Method(ctx context.Context, srvInfo *core.SrvInfo, req interface{}, handler func(context.Context, interface{}) (interface{}, error)) (ret interface{}, err error) {    defer func() {        if p := recover(); p != nil {            err = fmt.Errorf("internal error: %v", p)        }    }()    ret, err = handler(ctx, req) //所有下層邏輯全部在這個函數裡分發,所以我錯誤地認為任何panic都能在這裡recover    return ret, err}

總監:“goroutine發生panic,只有自身能夠recover,其它goroutine是抓不到的,這是常識啊。”
我:“......”
嚇的我啥也沒敢再說,趕緊把go關鍵字刪了,然後等總監走了之後,立馬上網研究了一波goroutine、panic、recover之間的關係,下面是結論。

結論

首先,要明確一點,panic會停止整個進程,不僅僅是當前goroutine,也就是說整個程式都會涼涼(我現在認為這就是goroutine沒有在代碼裡泛濫的原因之一,另外的原因是,我覺得在cpu核全部跑起來的情況下,開再多的goroutine也只能並發而不能並行)。
其次,panic是有序的、可控的停止程式,不是啪唧一下就宕掉了,所以我們還可以用recover補救。
然後,recover只能在defer裡面生效,如果不是在defer裡調用,會直接返回nil
最後,很重要的一點是:goroutine發生panic時,只會調用自身的defer,所以即便主goroutine裡寫了recover邏輯,也無法拯救到其它goroutine裡的panic
所以呢,之前的go cache.Set寫法是很危險的,因為cache裡沒有做任何recover,一旦出現panic,會影響到整個系統。
假設我一定裝這個逼用go關鍵字實現(顯然我不是這樣的人),代碼可以改成:

go func() {    defer func() {        if r := recover(); r != nil {            fmt.Println("don't worry, I can take care of myself")        }    }()    cache.Set(prefix+strCityIDs, &ret, 12*time.Hour) //存入緩衝}()
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.