panic(運(yùn)行時(shí)恐慌)
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”的含義是,這是一個(gè)runtime代碼包中拋出的panic。在這個(gè)panic中,包含了一個(gè)runtime.Error接口類(lèi)型的值。runtime.Error接口內(nèi)嵌了error接口并做了一點(diǎn)點(diǎn)擴(kuò)展,runtime包中有不少它的實(shí)現(xiàn)類(lèi)型。
實(shí)際上,此詳情中的“panic: ”右邊的內(nèi)容,正是這個(gè)panic包含的runtime.Error類(lèi)型值的字符串表示形式。
此外,panic詳情中一般還會(huì)包含與它的引發(fā)原因有關(guān)的goroutine的代碼執(zhí)行信息。正如前述詳情中的“goroutine 1 [running]”,它表示有一個(gè)ID為1的goroutine在此panic被引發(fā)的時(shí)候正在運(yùn)行。
注意,這里的ID其實(shí)并不重要,因?yàn)樗皇荊o語(yǔ)言運(yùn)行時(shí)系統(tǒng)內(nèi)部給予的一個(gè)goroutine編號(hào),我們?cè)诔绦蛑惺菬o(wú)法獲取和更改的。
我們?cè)倏聪乱恍校?“main.main()”表明了這個(gè)goroutine包裝的go函數(shù)就是命令源碼文件中的那個(gè)main函數(shù),也就是說(shuō)這里的goroutine正是主goroutine。再下面的一行,指出的就是這個(gè)goroutine中的哪一行
代碼在此panic被引發(fā)時(shí)正在執(zhí)行。
這包含了此行代碼在其所屬的源碼文件中的行數(shù),以及這個(gè)源碼文件的絕對(duì)路徑。這一行最后的+0x3d代表的是:此行代碼相對(duì)于其所屬函數(shù)的入口程序計(jì)數(shù)偏移量。不過(guò),一般情況下它的用戶并不大。
最后, “exit status 2”表明我的這個(gè)程序是以退出狀態(tài)碼2結(jié)束運(yùn)行的。在大多數(shù)操作系統(tǒng)中,只要退出狀態(tài)碼不是0,都意味著程序運(yùn)行的非正常結(jié)束。在Go語(yǔ)言中,因panic導(dǎo)致程序結(jié)束運(yùn)行的退出狀態(tài)碼一般都會(huì)是2。
從panic被引發(fā)到程序終止運(yùn)行的大致過(guò)程是什么?
?某個(gè)函數(shù)中的某行代碼有意或無(wú)意地引發(fā)了一個(gè)panic。這時(shí),初始的panic詳情會(huì)被建立起來(lái),并且該程序的控制權(quán)會(huì)立即從此行代碼轉(zhuǎn)移至調(diào)用其所屬函數(shù)的那行代碼上,也就是調(diào)用棧中的上一級(jí)。
?這也意味著,此行代碼所屬函數(shù)的執(zhí)行隨即終止。緊接著,控制權(quán)并不會(huì)在此有片刻停留,它又會(huì)立即轉(zhuǎn)移至再上一級(jí)的調(diào)用代碼處。控制權(quán)如此一級(jí)一級(jí)地沿著調(diào)用棧的反方向傳播至頂端,也就是我們編寫(xiě)的最外層函數(shù)那里。
?這里的外層函數(shù)指的就是go函數(shù),對(duì)于主goroutine來(lái)說(shuō)就是main函數(shù)。但是控制權(quán)也不會(huì)停留在那里,而是被Go語(yǔ)言運(yùn)行時(shí)系統(tǒng)收回。
?隨后,程序崩潰并終止運(yùn)行,承載程序這次運(yùn)行的進(jìn)程也會(huì)隨之死亡并消失。與此同時(shí),在這個(gè)控制權(quán)傳播的過(guò)程中,panic詳情會(huì)被逐漸地積累和完善,并會(huì)在程序終止之前被打印出來(lái)。
怎樣施加應(yīng)對(duì)panic的保護(hù)措施,從而避免程序崩潰?
?Go語(yǔ)言的內(nèi)建函數(shù)recover專用于恢復(fù)panic,或者說(shuō)平息運(yùn)行時(shí)恐慌。recover函數(shù)無(wú)需任何參數(shù),并且會(huì)返回一個(gè)空接口類(lèi)型的值。
?如果用法正確,這個(gè)值實(shí)際上就是即將恢復(fù)的panic包含的值。并且,如果這個(gè)panic是因我們調(diào)用panic函數(shù)而引發(fā)的,那么該值同時(shí)也會(huì)是我們此次調(diào)用panic函數(shù)時(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ù)值,先通過(guò)panic函數(shù)引發(fā)了一個(gè)panic,緊接著通過(guò)recover函數(shù)恢復(fù)這個(gè)panic。結(jié)果:程序依然會(huì)崩潰,因?yàn)閜anic一旦發(fā)生,控制權(quán)就會(huì)迅速地沿著調(diào)用棧的反方向傳播。所以,在panic函數(shù)調(diào)用之后的代碼,根本沒(méi)有執(zhí)行的機(jī)會(huì)。應(yīng)該使用defer語(yǔ)句,defer語(yǔ)句就是用來(lái)延遲執(zhí)行代碼的,延遲到該語(yǔ)句所在的函數(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.")
}
如果一個(gè)函數(shù)中有多條defer語(yǔ)句,那么幾個(gè)defer函數(shù)調(diào)用的執(zhí)行順序是怎樣的?
答: 在同一個(gè)函數(shù)中,defer函數(shù)調(diào)用的執(zhí)行順序與它們分別所屬的defer語(yǔ)句的出現(xiàn)順序(更嚴(yán)謹(jǐn)?shù)卣f(shuō),是執(zhí)行順序)完全相反。
?當(dāng)一個(gè)函數(shù)即將結(jié)束執(zhí)行時(shí),其中的寫(xiě)在最下邊的defer函數(shù)調(diào)用會(huì)最先執(zhí)行,其次是寫(xiě)在它上邊、與它的距離最近的那個(gè)defer函數(shù)調(diào)用,以此類(lèi)推,最上邊的defer函數(shù)調(diào)用會(huì)最后一個(gè)執(zhí)行。
?如果函數(shù)中有一條for語(yǔ)句,并且這條for語(yǔ)句中包含了一個(gè)defer語(yǔ)句,那么顯然這條defer語(yǔ)句的執(zhí)行次數(shù),就取決于for語(yǔ)句的迭代次數(shù)。
?并且,同一條defer語(yǔ)句每被執(zhí)行一次,其中的defer函數(shù)調(diào)用就會(huì)產(chǎn)生一次,并且,這些函數(shù)調(diào)用同樣不會(huì)被立即執(zhí)行。
?那么for語(yǔ)句中的多個(gè)defer函數(shù)的執(zhí)行順序:在defer語(yǔ)句每次執(zhí)行的時(shí)候,Go語(yǔ)句會(huì)把它攜帶的defer函數(shù)及其參數(shù)值另行存儲(chǔ)到一個(gè)隊(duì)列中。這個(gè)隊(duì)列與該defer語(yǔ)句所屬的函數(shù)時(shí)對(duì)應(yīng)的,并且,它是先進(jìn)后出(FILO)的,相當(dāng)于一個(gè)棧。在需要執(zhí)行某個(gè)函數(shù)中的defer函數(shù)調(diào)用的時(shí)候,Go語(yǔ)言會(huì)先拿到對(duì)應(yīng)的隊(duì)列,然后從該隊(duì)列中一個(gè)一個(gè)地取出defer函數(shù)及其參數(shù)值,并逐個(gè)執(zhí)行調(diào)用。