【Go】Panic函數

  panic(運行時恐慌)是一種只會在程序運行時纔回拋出來的異常。在panic被拋出以後,若是沒有在程序裏添加任何保護措施的話,程序就會在打印出panic的詳情,終止運行。

  若是一個panic是無心間引起的,其中的值只能由Go語言運行時系統給定,可是當使用painc函數有意引起一個panic時,卻能夠自行指定其包含的值。函數

  舉個栗子spa

package main

func main() {
    s1 := []int{0, 1, 2, 3, 4}
    e5 := s1[5]
    _ = e5
}

  運行上面的代碼,會拋出panic3d

panic: runtime error: index out of range

goroutine 1 [running]:   //Id爲1的goroutine在此panic被引起時正在運行
main.main()
 /Users/haolin/GeekTime/Golang_Puzzlers/src/puzzlers/article19/q0/demo47.go:5 +0x3d  //此行代碼在其所屬源碼文件中的行數,以及源碼文件的絕對路徑, +03d是計數偏移量,用處不大。
exit status 2  //以退出狀態碼2結束運行,通常狀態不爲0時表示程序非正常退出

 

 從Painc被引起到程序終止運行的大體過程是怎樣的?

  某個函數中的某行代碼引起了一個panic後,初始的panic詳情會被創建起來,而且該程序的控制器會當即今後行代碼轉移到調用其所屬函數的那行代碼上(調用棧中的上一級),此行代碼所屬函數的執行隨即終止。緊接着,控制權並不會在此有片刻停留,它又會當即轉移至上一級的調用代碼處,反方向傳播直至最外層函數(go函數,對於主goroutine來講就是main函數)。可是控制器也不會停留在那裏,而是被Go語言運行時系統收回。隨後程序奔潰並終止運行,承載程序此次運行的進程也會隨之死亡並消失。與此同時,在這個控制器傳播過程當中,panic詳情會積累和完善,並在程序終止以前打印出來。code

//main函數調用了caller1函數,caller1函數調用了caller2函數
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函數,把某個值做爲參數傳給該函數blog

  因爲panic函數的惟一一個參數時空接口類型的,因此從語法上講,它能夠接受任何類型的值。但最好傳入errir類型的錯誤值,或者其餘能夠被有效序列化(能夠更易讀地去表示形式轉換)的值。使程序崩潰時,panic包含的那個值字符串表示形式會被打印出來接口

 

怎樣施加panic的保護措施,避免程序奔潰?

  Go語言的內建函數recover專用於恢復panic。recover函數無需任何參數,而且會返回一個空接口類型的值。若是用法正確,這個值實際上就是即將恢復的panic包含的值,而且若是這個panic是因咱們調用panic函數引起的,那麼該值同時也會是這次調用panic函數時,傳入的參數值副本。隊列

  上面強調用法正確,那什麼是不正確的用法?進程

package main

import (
 "fmt"
 "errors"
)

func main() {
 fmt.Println("Enter function main.")
 // 引起 panic。
 panic(errors.New("something wrong"))
 p := recover()
 fmt.Printf("panic: %s\n", p)
 fmt.Println("Exit function main.")
}

  在上面這個函數中先經過調用panic函數引起一個panic,緊接着想經過調用recover函數恢復這個panic。但這個recover函數不會起到任何做用,由於panic一旦發生,控制權會沿着調用棧反方向傳播,因此在panic函數調用以後的代碼,根本沒有執行的機會。字符串

  那若是把recover函數的代碼提早呢?即先調用recover函數,再調用panic函數。這樣也不行,由於在調用recover函數時未發生panic,那麼該函數就不會作任何事情,只會返回一個nil。源碼

  那怎樣纔是正確的作法呢?

  這就要用到defer語句。defer語句是被用來延遲執行代碼的。延遲到該語句所在的函數即將執行結束的那一刻,不管結束執行的緣由是什麼,即便致使它執行結束的緣由是一個panic,所以聯用defer語句和recover函數調用,可以恢復一個已經發生的panic

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.Printf("panic: %s\n", p)
  }
  fmt.Println("Exit defer function.")
 }()
 // 引起 panic。
 panic(errors.New("something wrong"))
 fmt.Println("Exit function main.")
}

   儘可能把defer語句寫在函數體開始處,由於在引起panic的語句以後的全部語句,都不會有任何執行機會。只有這樣defer函數中的recover函數調用纔會攔截,並恢復defer語句所屬的函數,及其調用代碼中發生的全部panic  

 

  若是一個函數中有多條defer語句,那麼那幾個defer函數調用的執行順序是怎樣的?

  在同一個函數中,defer函數調用的執行順序和它們所屬的defer語句的出現順序徹底相反。當一個函數即將結束執行時,寫在最下面的defer函數調用會最早執行,其次是寫在它上邊的與它距離最近的defer函數調用,以此類推 

package main

import "fmt"

func main() {
    defer fmt.Println("first defer")
    for i := 0; i < 3; i++ {
        defer fmt.Printf("defer in for [%d]\n", i)
    }
    defer fmt.Println("last defer")
}


//運行結果
last defer
defer in for [2]
defer in for [1]
defer in for [0]
first defer

  若是for語句中包含一條defer語句,那這條defer語句執行次數,就取決於for語句迭代次數。而且同一條defer語句每被執行一次,其中的defer調用就會產生一次,並且這些函數調用一樣不會被當即執行。在defer執行時,Go語言會把它攜帶的defer函數及其參數值另行存儲到一個先進後出的隊列,至關於一個棧。在須要執行某個函數中defer函數調用時,Go語言會先拿到對應的隊列,而後從該隊列中一個一個取出defer函數及其參數值,並逐個執行調用

相關文章
相關標籤/搜索