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)程膘盖。