Defer, Panic, Recover

1纵顾、簡介

Go具有控制流程的常用機(jī)制:if,for间唉,switch绞灼,goto。 它還有g(shù)o語句在單獨(dú)的goroutine中運(yùn)行代碼呈野。 在這里低矮,我想討論一些不太常見的問題:Defer,Panic和Recover被冒。

2军掂、Defer

Defer語句將函數(shù)調(diào)用推送到列表中。 周圍函數(shù)返回后執(zhí)行已保存調(diào)用的列表昨悼。 Defer通常用于簡化執(zhí)行各種清理操作的功能蝗锥。
例如,讓我們看一個(gè)打開兩個(gè)文件并將一個(gè)文件的內(nèi)容復(fù)制到另一個(gè)文件的函數(shù):

func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }

    dst, err := os.Create(dstName)
    if err != nil {
        return
    }

    written, err = io.Copy(dst, src)
    dst.Close()
    src.Close()
    return
}

這樣確實(shí)能正常運(yùn)行率触,但有一個(gè)錯(cuò)誤终议。 如果對(duì)os.Create的調(diào)用失敗,該函數(shù)將返回而不關(guān)閉源文件葱蝗。 這可以通過在第二個(gè)return語句之前調(diào)用src.Close來輕松解決穴张,但如果函數(shù)更復(fù)雜,則問題可能不會(huì)那么容易被注意到并解決两曼。 通過引入Defer語句陆馁,我們可以確保文件始終關(guān)閉:

func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }
    defer src.Close()

    dst, err := os.Create(dstName)
    if err != nil {
        return
    }
    defer dst.Close()

    return io.Copy(dst, src)
}

Defer語句允許我們考慮在打開它之后立即關(guān)閉每個(gè)文件,保證無論函數(shù)中的返回語句數(shù)量如何合愈,文件都將被關(guān)閉。
Defer語句的行為是直截了當(dāng)且可預(yù)測的击狮。 有三個(gè)簡單的規(guī)則:

2.1佛析、在計(jì)算defer語句時(shí),將計(jì)算延遲函數(shù)的參數(shù)彪蓬。

在此示例中寸莫,在延遲Println調(diào)用時(shí)計(jì)算表達(dá)式“i”。 函數(shù)返回后档冬,延遲調(diào)用將打印“0”膘茎。

func a() {
    i := 0
    defer fmt.Println(i)
    i++
    return
}

2.2、在周圍函數(shù)返回后酷誓,延遲函數(shù)調(diào)用以Last In First Out順序執(zhí)行披坏。

如下函數(shù)將打印“3210”:

func b() {
    for i := 0; i < 4; i++ {
        defer fmt.Print(i)
    }
}

2.3、Defer函數(shù)可以讀取并分配給返回函數(shù)的命名返回值盐数。

在如下示例中棒拂,Defer函數(shù)在周圍函數(shù)返回后遞增返回值i。 因此,此函數(shù)最終返回 2:

func c() (i int) {
    defer func() { i++ }()
    return 1
}

這樣便于修改函數(shù)的錯(cuò)誤返回值帚屉;

3谜诫、Panic 和 Recover

Panic是一個(gè)內(nèi)置函數(shù),可以阻止普通的控制流攻旦。 當(dāng)函數(shù)F調(diào)用panic時(shí)喻旷,F(xiàn)的執(zhí)行停止,F(xiàn)中的任何Defer函數(shù)都正常執(zhí)行牢屋,然后將F返回其調(diào)用者且预。 對(duì)于調(diào)用者,只會(huì)感受F為Panic伟阔。 該過程繼續(xù)向上移動(dòng)辣之,直到當(dāng)前goroutine中的所有函數(shù)都返回,此時(shí) 程序崩潰皱炉。 可以通過直接調(diào)用 panic 來啟動(dòng) panic怀估。 它們也可能由運(yùn)行時(shí)錯(cuò)誤引起邀桑,例如越界數(shù)組訪問疆栏。
Recover是一個(gè)內(nèi)置函數(shù),可以重新控制 panic 的goroutine侧甫。 recover僅在defer函數(shù)內(nèi)有用灾部。 在正常執(zhí)行期間康铭,對(duì)recover的調(diào)用將返回nil并且沒有其他效果。 如果當(dāng)前goroutine處于 panic 狀態(tài)赌髓,則對(duì) recover 的調(diào)用將捕獲 panic 并恢復(fù)正常執(zhí)行从藤。
這是一個(gè)演示panic和defer機(jī)制的示例程序:

package main

import "fmt"

func main() {
    f()
    fmt.Println("Returned normally from f.")
}

func f() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered in f", r)
        }
    }()
    fmt.Println("Calling g.")
    g(0)
    fmt.Println("Returned normally from g.")
}

func g(i int) {
    if i > 3 {
        fmt.Println("Panicking!")
        panic(fmt.Sprintf("%v", i))
    }
    defer fmt.Println("Defer in g", i)
    fmt.Println("Printing in g", i)
    g(i + 1)
}

函數(shù)g獲取 int i,如果i大于3則發(fā)生panic锁蠕,否則它用參數(shù) i + 1 進(jìn)行遞歸調(diào)用夷野。 函數(shù) f 推出一個(gè)調(diào)用recover并打印恢復(fù)值的函數(shù)(如果它是非零的)。 嘗試在閱讀之前描繪該程序的輸出荣倾。
該程序?qū)⑤敵觯?/p>

Calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
Panicking!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
Recovered in f 4
Returned normally from f.

如果我們從f中刪除defer函數(shù)悯搔,則不會(huì)恢復(fù)panic并到達(dá)goroutine調(diào)用堆棧的頂部,從而終止程序舌仍。 此修改后的程序?qū)⑤敵觯?/p>

Calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
Panicking!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
panic: 4
 
panic PC=0x2a9cd8
[stack trace omitted]

有關(guān)panic和recover的真實(shí)示例妒貌,請(qǐng)參閱Go標(biāo)準(zhǔn)庫中的json包。 它使用一組遞歸函數(shù)對(duì)JSON編碼的數(shù)據(jù)進(jìn)行解碼铸豁。 當(dāng)遇到格式錯(cuò)誤的JSON時(shí)灌曙,解析器調(diào)用panic將堆棧展開到頂級(jí)函數(shù)調(diào)用,該函數(shù)調(diào)用從panic中恢復(fù)并返回適當(dāng)?shù)腻e(cuò)誤值(請(qǐng)參閱 decode.go 中的decodeState類型的'error'和'unmarshal'方法)推姻。
Go 庫的原則是即使在包的內(nèi)部使用了 panic平匈,在它的對(duì)外接口(API)中也必須用 recover 處理成返回顯式的錯(cuò)誤。

4、其他

4.1增炭、Defer類似Java中finally

使用過程中忍燥,defer類似Java中finally,即使panic(即java中 throw exception)隙姿,依然能夠執(zhí)行

4.2梅垄、panic 只能在本 goroutine 處理

若嘗試在main中recover goroutine中panic,將無法達(dá)到預(yù)期输玷,程序仍然會(huì)結(jié)束

4.3队丝、recover 只能在 defer 中有效

golang的要求,recover只能寫在defer中

4.4欲鹏、多使用recover除占用cpu外机久,不會(huì)影響服務(wù)正常

如果函數(shù)沒有 panic,調(diào)用 recover 函數(shù)不會(huì)獲取到任何信息赔嚎,也不會(huì)影響當(dāng)前進(jìn)程膘盖。

5、參考文獻(xiàn)

  1. Defer, Panic, and Recover
  2. Golang: 深入理解panic and recover
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末尤误,一起剝皮案震驚了整個(gè)濱河市侠畔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌损晤,老刑警劉巖软棺,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異尤勋,居然都是意外死亡喘落,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門最冰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來揖盘,“玉大人,你說我怎么就攤上這事锌奴。” “怎么了憾股?”我有些...
    開封第一講書人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵鹿蜀,是天一觀的道長。 經(jīng)常有香客問我服球,道長茴恰,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任斩熊,我火速辦了婚禮往枣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己分冈,他們只是感情好圾另,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著雕沉,像睡著了一般集乔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上坡椒,一...
    開封第一講書人閱讀 51,301評(píng)論 1 301
  • 那天扰路,我揣著相機(jī)與錄音,去河邊找鬼倔叼。 笑死汗唱,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的丈攒。 我是一名探鬼主播哩罪,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼肥印!你這毒婦竟也來了识椰?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤深碱,失蹤者是張志新(化名)和其女友劉穎腹鹉,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體敷硅,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡功咒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了绞蹦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片力奋。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖幽七,靈堂內(nèi)的尸體忽然破棺而出景殷,到底是詐尸還是另有隱情,我是刑警寧澤澡屡,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布猿挚,位于F島的核電站,受9級(jí)特大地震影響驶鹉,放射性物質(zhì)發(fā)生泄漏绩蜻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一室埋、第九天 我趴在偏房一處隱蔽的房頂上張望办绝。 院中可真熱鬧伊约,春花似錦、人聲如沸孕蝉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽昔驱。三九已至疹尾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間骤肛,已是汗流浹背纳本。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留腋颠,地道東北人繁成。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像淑玫,于是被迫代替她去往敵國和親巾腕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

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