Go語言核心36講23

2022-10-09 09:30:08 字數 4226 閱讀 4552

我在上兩篇文章中,詳細地講述了go語言中的錯誤處理,並從兩個視角為你總結了錯誤型別、錯誤值的處理技巧和設計方式。

在本篇,我要給你展示go語言的另外一種錯誤處理方式。不過,嚴格來說,它處理的不是錯誤,而是異常,並且是一種在我們意料之外的程式異常。

這種程式異常被叫做panic,我把它翻譯為執行時恐慌。其中的「恐慌」二字是由panic直譯過來的,而之所以前面又加上了「執行時」三個字,是因為這種異常只會在程式執行的時候被丟擲來。

我們舉個具體的例子來看看。

比如說,乙個go程式裡有乙個切片,它的長度是5,也就是說該切片中的元素值的索引分別為01234,但是,我在程式裡卻想通過索引5訪問其中的元素值,顯而易見,這樣的訪問是不正確的。

go程式,確切地說是程式內嵌的go語言執行時系統,會在執行到這行**的時候丟擲乙個「index out of range」的panic,用以提示你索引越界了。

當然了,這不僅僅是個提示。當panic被丟擲之後,如果我們沒有在程式裡新增任何保護措施的話,程式(或者說代表它的那個程序)就會在列印出panic的詳細情況(以下簡稱panic詳情)之後,終止執行。

現在,就讓我們來看一下這樣的panic詳情中都有什麼。

panic: runtime error: index out of range

goroutine 1 [running]:

main.main()

/users/haolin/geektime/golang_puzzlers/src/puzzlers/article19/q0/demo47.go:5 +0x3d

exit status 2

這份詳情的第一行是「panic: runtime error: index out of range」。其中的「runtime error」的含義是,這是乙個runtime**包中丟擲的panic。在這個panic中,包含了乙個runtime.error介面型別的值。runtime.error介面內嵌了error介面,並做了一點點擴充套件,runtime包中有不少它的實現型別。

實際上,此詳情中的「panic:」右邊的內容,正是這個panic包含的runtime.error型別值的字串表示形式。

此外,panic詳情中,一般還會包含與它的引發原因有關的goroutine的**執行資訊。正如前述詳情中的「goroutine 1 [running]」,它表示有乙個id為1的goroutine在此panic被引發的時候正在執行。

注意,這裡的id其實並不重要,因為它只是go語言執行時系統內部給予的乙個goroutine編號,我們在程式中是無法獲取和更改的。

我們再看下一行,「main.main()」表明了這個goroutine包裝的go函式就是命令原始碼檔案中的那個main函式,也就是說這裡的goroutine正是主goroutine。再下面的一行,指出的就是這個goroutine中的哪一行**在此panic被引發時正在執行。

這包含了此行**在其所屬的原始碼檔案中的行數,以及這個原始碼檔案的絕對路徑。這一行最後的+0x3d代表的是:此行**相對於其所屬函式的入口程式計數偏移量。不過,一般情況下它的用處並不大。

最後,「exit status 2」表明我的這個程式是以退出狀態碼2結束執行的。在大多數作業系統中,只要退出狀態碼不是0,都意味著程式執行的非正常結束。在go語言中,因panic導致程式結束執行的退出狀態碼一般都會是2

綜上所述,我們從上邊的這個panic詳情可以看出,作為此panic的引發根源的**處於demo47.go檔案中的第5行,同時被包含在main包(也就是命令原始碼檔案所在的**包)的main函式中。

那麼,我的第乙個問題也隨之而來了。我今天的問題是:從panic被引發到程式終止執行的大致過程是什麼?

這道題的典型回答是這樣的。

我們先說乙個大致的過程:某個函式中的某行**有意或無意地引發了乙個panic。這時,初始的panic詳情會被建立起來,並且該程式的控制權會立即從此行**轉移至呼叫其所屬函式的那行**上,也就是呼叫棧中的上一級。

這也意味著,此行**所屬函式的執行隨即終止。緊接著,控制權並不會在此有片刻的停留,它又會立即轉移至再上一級的呼叫**處。控制權如此一級一級地沿著呼叫棧的反方向傳播至頂端,也就是我們編寫的最外層函式那裡。

這裡的最外層函式指的是go函式,對於主goroutine來說就是main函式。但是控制權也不會停留在那裡,而是被go語言執行時系統收回。

隨後,程式崩潰並終止執行,承載程式這次執行的程序也會隨之死亡並消失。與此同時,在這個控制權傳播的過程中,panic詳情會被逐漸地積累和完善,並會在程式終止之前被列印出來。

panic可能是我們在無意間(或者說一不小心)引發的,如前文所述的索引越界。這類panic是真正的、在我們意料之外的程式異常。不過,除此之外,我們還是可以有意地引發panic。

go語言的內建函式panic是專門用於引發panic的。panic函式使程式開發者可以在程式執行期間報告異常。

注意,這與從函式返回錯誤值的意義是完全不同的。當我們的函式返回乙個非nil的錯誤值時,函式的呼叫方有權選擇不處理,並且不處理的後果往往是不致命的。

這裡的「不致命」的意思是,不至於使程式無法提供任何功能(也可以說僵死)或者直接崩潰並終止執行(也就是真死)。

但是,當乙個panic發生時,如果我們不施加任何保護措施,那麼導致的直接後果就是程式崩潰,就像前面描述的那樣,這顯然是致命的。

為了更清楚地展示答案中描述的過程,我編寫了demo48.go檔案。你可以先檢視一下其中的**,再試著執行它,並體會它列印的內容所代表的含義。

我在這裡再提示一點。panic詳情會在控制權傳播的過程中,被逐漸地積累和完善,並且,控制權會一級一級地沿著呼叫棧的反方向傳播至頂端。

因此,在針對某個goroutine的**執行資訊中,呼叫棧底端的資訊會先出現,然後是上一級呼叫的資訊,以此類推,最後才是此呼叫棧頂端的資訊。

比如,main函式呼叫了caller1函式,而caller1函式又呼叫了caller2函式,那麼caller2函式中**的執行資訊會先出現,然後是caller1函式中**的執行資訊,最後才是main函式的資訊。

goroutine 1 [running]:

main.caller2()

/users/haolin/geektime/golang_puzzlers/src/puzzlers/article19/q1/demo48.go:22 +0x91

main.caller1()

/users/haolin/geektime/golang_puzzlers/src/puzzlers/article19/q1/demo48.go:15 +0x66

main.main()

/users/haolin/geektime/golang_puzzlers/src/puzzlers/article19/q1/demo48.go:9 +0x66

exit status 2

(從panic到程式崩潰)

好了,到這裡,我相信你已經對panic被引發後的程式終止過程有一定的了解了。深入地了解此過程,以及正確地解讀panic詳情應該是我們的必備技能,這在除錯go程式或者為go程式排查錯誤的時候非常重要。

最近的兩篇文章,我們是圍繞著panic函式、recover函式以及defer語句進行的。今天我主要講了panic函式。這個函式是專門被用來引發panic的。panic也可以被稱為執行時恐慌,它是一種只能在程式執行期間丟擲的程式異常。

go語言的執行時系統可能會在程式出現嚴重錯誤時自動地丟擲panic,我們在需要時也可以通過呼叫panic函式引發panic。但不論怎樣,如果不加以處理,panic就會導致程式崩潰並終止執行。

乙個函式怎樣才能把panic轉化為error型別值,並將其作為函式的結果值返回給呼叫方?

戳此檢視go語言專欄文章配套詳細**。

go語言核心36講要點概括(6 10)

06 程式實體的那些事兒 下 package main import fmt var container string func main 輸出one println container 1 如何判斷container型別 value,ok inte ce container string 它包括了...

Go語言核心36講筆記 程式實體那些事

1 變數有兩種宣告方式 var name string 完整變數宣告 name yan 短變數宣告2 如果內層變數使用 去定義,會產生內部變數 但不影響外部變數 如果內層的賦值用 則會直接使用外部變數操作,會對外部變數造成修改 如果內層的賦值用 的時候不能加var,加了var還是新變數。func f...

go語言核心程式設計 Go 語言核心檔案除錯

程式除錯對於檢查和理解程式執行過程和狀態是非常有用的。乙個核心轉儲檔案 core dump file 中包含程式程序執行時的記憶體資訊和程序狀態。它主要用於程式的問題除錯,以及在執行過程中理解程式的狀態。這些對於我們診斷程式問題原因和分析生產環境中的服務問題有非常大的幫助。在本文中,我會用乙個非常簡...