panic(運(yùn)行時恐慌)
demo:
fmt.Println("Enter function caller2.")
s1 := []int{0, 1, 2, 3, 4}
e5 := s1[5]
1 panic: runtime error: index of range
2
3 goroutine 1 [running]:
4 main.main()
5 /User/zheng/Golang_Puzzlers/demo.go:5 +0x3d
exit statu 2
這里的第一行是“panic: runtime error: index out of range”橄教。其中的“runtime error”的含義是,這是一個runtime代碼包中拋出的panic。在這個panic中证舟,包含了一個runtime.Error接口類型的值硕旗。runtime.Error接口內(nèi)嵌了error接口并做了一點(diǎn)點(diǎn)擴(kuò)展,runtime包中有不少它的實(shí)現(xiàn)類型女责。
實(shí)際上漆枚,此詳情中的“panic: ”右邊的內(nèi)容,正是這個panic包含的runtime.Error類型值的字符串表示形式抵知。
此外墙基,panic詳情中一般還會包含與它的引發(fā)原因有關(guān)的goroutine的代碼執(zhí)行信息。正如前述詳情中的“goroutine 1 [running]”,它表示有一個ID為1的goroutine在此panic被引發(fā)的時候正在運(yùn)行刷喜。
注意残制,這里的ID其實(shí)并不重要,因?yàn)樗皇荊o語言運(yùn)行時系統(tǒng)內(nèi)部給予的一個goroutine編號掖疮,我們在程序中是無法獲取和更改的初茶。
我們再看下一行, “main.main()”表明了這個goroutine包裝的go函數(shù)就是命令源碼文件中的那個main函數(shù)浊闪,也就是說這里的goroutine正是主goroutine恼布。再下面的一行,指出的就是這個goroutine中的哪一行
代碼在此panic被引發(fā)時正在執(zhí)行搁宾。
這包含了此行代碼在其所屬的源碼文件中的行數(shù)折汞,以及這個源碼文件的絕對路徑。這一行最后的+0x3d代表的是:此行代碼相對于其所屬函數(shù)的入口程序計數(shù)偏移量盖腿。不過爽待,一般情況下它的用戶并不大。
最后奸忽, “exit status 2”表明我的這個程序是以退出狀態(tài)碼2結(jié)束運(yùn)行的堕伪。在大多數(shù)操作系統(tǒng)中,只要退出狀態(tài)碼不是0栗菜,都意味著程序運(yùn)行的非正常結(jié)束欠雌。在Go語言中,因panic導(dǎo)致程序結(jié)束運(yùn)行的退出狀態(tài)碼一般都會是2疙筹。
從panic被引發(fā)到程序終止運(yùn)行的大致過程是什么富俄?
?某個函數(shù)中的某行代碼有意或無意地引發(fā)了一個panic。這時而咆,初始的panic詳情會被建立起來霍比,并且該程序的控制權(quán)會立即從此行代碼轉(zhuǎn)移至調(diào)用其所屬函數(shù)的那行代碼上,也就是調(diào)用棧中的上一級暴备。
?這也意味著悠瞬,此行代碼所屬函數(shù)的執(zhí)行隨即終止。緊接著,控制權(quán)并不會在此有片刻停留浅妆,它又會立即轉(zhuǎn)移至再上一級的調(diào)用代碼處望迎。控制權(quán)如此一級一級地沿著調(diào)用棧的反方向傳播至頂端凌外,也就是我們編寫的最外層函數(shù)那里辩尊。
?這里的外層函數(shù)指的就是go函數(shù),對于主goroutine來說就是main函數(shù)康辑。但是控制權(quán)也不會停留在那里摄欲,而是被Go語言運(yùn)行時系統(tǒng)收回。
?隨后疮薇,程序崩潰并終止運(yùn)行胸墙,承載程序這次運(yùn)行的進(jìn)程也會隨之死亡并消失。與此同時惦辛,在這個控制權(quán)傳播的過程中劳秋,panic詳情會被逐漸地積累和完善,并會在程序終止之前被打印出來胖齐。
怎樣施加應(yīng)對panic的保護(hù)措施玻淑,從而避免程序崩潰?
?Go語言的內(nèi)建函數(shù)recover專用于恢復(fù)panic,或者說平息運(yùn)行時恐慌呀伙。recover函數(shù)無需任何參數(shù)补履,并且會返回一個空接口類型的值。
?如果用法正確剿另,這個值實(shí)際上就是即將恢復(fù)的panic包含的值箫锤。并且,如果這個panic是因我們調(diào)用panic函數(shù)而引發(fā)的雨女,那么該值同時也會是我們此次調(diào)用panic函數(shù)時谚攒,傳入的參數(shù)值副本。
package main
import (
"fmt"
"errors"
)
func main() {
fmt.Println("Enter function main.")
// 引發(fā)panic
panic(errors.New("something wrong"))
p := recover()
fmt.Printf("panic: %s\n", p)
fmt.Println("Exit function main.")
}
&emsp:在上面的函數(shù)值氛堕,先通過panic函數(shù)引發(fā)了一個panic馏臭,緊接著通過recover函數(shù)恢復(fù)這個panic。結(jié)果:程序依然會崩潰讼稚,因?yàn)閜anic一旦發(fā)生括儒,控制權(quán)就會迅速地沿著調(diào)用棧的反方向傳播。所以锐想,在panic函數(shù)調(diào)用之后的代碼帮寻,根本沒有執(zhí)行的機(jī)會。應(yīng)該使用defer語句赠摇,defer語句就是用來延遲執(zhí)行代碼的固逗,延遲到該語句所在的函數(shù)即將執(zhí)行結(jié)束的那一刻浅蚪。
package main
import (
"fmt"
"errors"
)
func main() {
fmt.Println("Enter function main.")
defer func(){
fmt.Println("Enter defer function.")
if p := recover(); p != nil {
fmt.Println("panic: %s\n",p)
}
}
// 引發(fā)panic
panic(errors.New("something wrong"))
fmt.Println("Exit function main.")
}
如果一個函數(shù)中有多條defer語句,那么幾個defer函數(shù)調(diào)用的執(zhí)行順序是怎樣的抒蚜?
答: 在同一個函數(shù)中掘鄙,defer函數(shù)調(diào)用的執(zhí)行順序與它們分別所屬的defer語句的出現(xiàn)順序(更嚴(yán)謹(jǐn)?shù)卣f,是執(zhí)行順序)完全相反嗡髓。
?當(dāng)一個函數(shù)即將結(jié)束執(zhí)行時,其中的寫在最下邊的defer函數(shù)調(diào)用會最先執(zhí)行收津,其次是寫在它上邊饿这、與它的距離最近的那個defer函數(shù)調(diào)用,以此類推撞秋,最上邊的defer函數(shù)調(diào)用會最后一個執(zhí)行长捧。
?如果函數(shù)中有一條for語句,并且這條for語句中包含了一個defer語句吻贿,那么顯然這條defer語句的執(zhí)行次數(shù)串结,就取決于for語句的迭代次數(shù)。
?并且舅列,同一條defer語句每被執(zhí)行一次肌割,其中的defer函數(shù)調(diào)用就會產(chǎn)生一次,并且帐要,這些函數(shù)調(diào)用同樣不會被立即執(zhí)行把敞。
?那么for語句中的多個defer函數(shù)的執(zhí)行順序:在defer語句每次執(zhí)行的時候,Go語句會把它攜帶的defer函數(shù)及其參數(shù)值另行存儲到一個隊列中榨惠。這個隊列與該defer語句所屬的函數(shù)時對應(yīng)的奋早,并且,它是先進(jìn)后出(FILO)的赠橙,相當(dāng)于一個棧耽装。在需要執(zhí)行某個函數(shù)中的defer函數(shù)調(diào)用的時候,Go語言會先拿到對應(yīng)的隊列期揪,然后從該隊列中一個一個地取出defer函數(shù)及其參數(shù)值掉奄,并逐個執(zhí)行調(diào)用。