<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Go 經(jīng)典入門(mén)系列 32:panic 和 recover

          共 10696字,需瀏覽 22分鐘

           ·

          2021-01-05 01:10

          點(diǎn)擊上方藍(lán)色“Go語(yǔ)言中文網(wǎng)”關(guān)注,每天一起學(xué) Go

          panic 和 recover

          歡迎來(lái)到 Golang 系列教程[1]的第 32 篇。

          什么是 panic?

          在 Go 語(yǔ)言中,程序中一般是使用錯(cuò)誤[2]來(lái)處理異常情況。對(duì)于程序中出現(xiàn)的大部分異常情況,錯(cuò)誤就已經(jīng)夠用了。

          但在有些情況,當(dāng)程序發(fā)生異常時(shí),無(wú)法繼續(xù)運(yùn)行。在這種情況下,我們會(huì)使用 panic 來(lái)終止程序。當(dāng)函數(shù)[3]發(fā)生 panic 時(shí),它會(huì)終止運(yùn)行,在執(zhí)行完所有的延遲[4]函數(shù)后,程序控制返回到該函數(shù)的調(diào)用方。這樣的過(guò)程會(huì)一直持續(xù)下去,直到當(dāng)前協(xié)程[5]的所有函數(shù)都返回退出,然后程序會(huì)打印出 panic 信息,接著打印出堆棧跟蹤(Stack Trace),最后程序終止。在編寫(xiě)一個(gè)示例程序后,我們就能很好地理解這個(gè)概念了。

          在本教程里,我們還會(huì)接著討論,當(dāng)程序發(fā)生 panic 時(shí),使用 recover 可以重新獲得對(duì)該程序的控制。

          可以認(rèn)為 panicrecover 與其他語(yǔ)言中的 try-catch-finally 語(yǔ)句類(lèi)似,只不過(guò)一般我們很少使用 panicrecover。而當(dāng)我們使用了 panicrecover 時(shí),也會(huì)比 try-catch-finally 更加優(yōu)雅,代碼更加整潔。

          什么時(shí)候應(yīng)該使用 panic?

          需要注意的是,你應(yīng)該盡可能地使用錯(cuò)誤[6],而不是使用 panic 和 recover。只有當(dāng)程序不能繼續(xù)運(yùn)行的時(shí)候,才應(yīng)該使用 panic 和 recover 機(jī)制

          panic 有兩個(gè)合理的用例。

          1. 發(fā)生了一個(gè)不能恢復(fù)的錯(cuò)誤,此時(shí)程序不能繼續(xù)運(yùn)行。一個(gè)例子就是 web 服務(wù)器無(wú)法綁定所要求的端口。在這種情況下,就應(yīng)該使用 panic,因?yàn)槿绻荒芙壎ǘ丝冢兑沧霾涣恕?/p>

          2. 發(fā)生了一個(gè)編程上的錯(cuò)誤。假如我們有一個(gè)接收指針參數(shù)的方法,而其他人使用 nil 作為參數(shù)調(diào)用了它。在這種情況下,我們可以使用 panic,因?yàn)檫@是一個(gè)編程錯(cuò)誤:用 nil 參數(shù)調(diào)用了一個(gè)只能接收合法指針的方法。

          panic 示例

          內(nèi)建函數(shù) panic 的簽名如下所示:

          func?panic(interface{})

          當(dāng)程序終止時(shí),會(huì)打印傳入 panic 的參數(shù)。我們寫(xiě)一個(gè)示例,你就會(huì)清楚它的用途了。我們現(xiàn)在就開(kāi)始吧。

          我們會(huì)寫(xiě)一個(gè)例子,來(lái)展示 panic 如何工作。

          package?main

          import?(
          ????"fmt"
          )

          func?fullName(firstName?*string,?lastName?*string)?{
          ????if?firstName?==?nil?{
          ????????panic("runtime?error:?first?name?cannot?be?nil")
          ????}
          ????if?lastName?==?nil?{
          ????????panic("runtime?error:?last?name?cannot?be?nil")
          ????}
          ????fmt.Printf("%s?%s\n",?*firstName,?*lastName)
          ????fmt.Println("returned?normally?from?fullName")
          }

          func?main()?{
          ????firstName?:=?"Elon"
          ????fullName(&firstName,?nil)
          ????fmt.Println("returned?normally?from?main")
          }

          在 playground 上運(yùn)行[7]

          上面的程序很簡(jiǎn)單,會(huì)打印一個(gè)人的全名。第 7 行的 fullName 函數(shù)會(huì)打印出一個(gè)人的全名。該函數(shù)在第 8 行和第 11 行分別檢查了 firstNamelastName 的指針是否為 nil。如果是 nilfullName 函數(shù)會(huì)調(diào)用含有不同的錯(cuò)誤信息的 panic。當(dāng)程序終止時(shí),會(huì)打印出該錯(cuò)誤信息。

          運(yùn)行該程序,會(huì)有如下輸出:

          panic:?runtime?error:?last?name?cannot?be?nil

          goroutine?1?[running]:
          main.fullName(0x1040c128,?0x0)
          ????/tmp/sandbox135038844/main.go:12?+0x120
          main.main()
          ????/tmp/sandbox135038844/main.go:20?+0x80

          我們來(lái)分析這個(gè)輸出,理解一下 panic 是如何工作的,并且思考當(dāng)程序發(fā)生 panic 時(shí),會(huì)怎樣打印堆棧跟蹤。

          在第 19 行,我們將 Elon 賦值給了 firstName。在第 20 行,我們調(diào)用了 fullName 函數(shù),其中 lastName 等于 nil。因此,滿(mǎn)足了第 11 行的條件,程序發(fā)生 panic。當(dāng)出現(xiàn)了 panic 時(shí),程序就會(huì)終止運(yùn)行,打印出傳入 panic 的參數(shù),接著打印出堆棧跟蹤。因此,第 14 行和第 15 行的代碼并不會(huì)在發(fā)生 panic 之后執(zhí)行。程序首先會(huì)打印出傳入 panic 函數(shù)的信息:

          panic:?runtime?error:?last?name?cannot?be?empty

          接著打印出堆棧跟蹤。

          程序在 fullName 函數(shù)的第 12 行發(fā)生 panic,因此,首先會(huì)打印出如下所示的輸出。

          main.fullName(0x1040c128,?0x0)
          ????/tmp/sandbox135038844/main.go:12?+0x120

          接著會(huì)打印出堆棧的下一項(xiàng)。在本例中,堆棧跟蹤中的下一項(xiàng)是第 20 行(因?yàn)榘l(fā)生 panic 的 fullName 調(diào)用就在這一行),因此接下來(lái)會(huì)打印出:

          main.main()
          ????/tmp/sandbox135038844/main.go:20?+0x80

          現(xiàn)在我們已經(jīng)到達(dá)了導(dǎo)致 panic 的頂層函數(shù),這里沒(méi)有更多的層級(jí),因此結(jié)束打印。

          發(fā)生 panic 時(shí)的 defer

          我們重新總結(jié)一下 panic 做了什么。當(dāng)函數(shù)發(fā)生 panic 時(shí),它會(huì)終止運(yùn)行,在執(zhí)行完所有的延遲函數(shù)后,程序控制返回到該函數(shù)的調(diào)用方。這樣的過(guò)程會(huì)一直持續(xù)下去,直到當(dāng)前協(xié)程的所有函數(shù)都返回退出,然后程序會(huì)打印出 panic 信息,接著打印出堆棧跟蹤,最后程序終止

          在上面的例子中,我們沒(méi)有延遲調(diào)用任何函數(shù)。如果有延遲函數(shù),會(huì)先調(diào)用它,然后程序控制返回到函數(shù)調(diào)用方。

          我們來(lái)修改上面的示例,使用一個(gè)延遲語(yǔ)句。

          package?main

          import?(
          ????"fmt"
          )

          func?fullName(firstName?*string,?lastName?*string)?{
          ????defer?fmt.Println("deferred?call?in?fullName")
          ????if?firstName?==?nil?{
          ????????panic("runtime?error:?first?name?cannot?be?nil")
          ????}
          ????if?lastName?==?nil?{
          ????????panic("runtime?error:?last?name?cannot?be?nil")
          ????}
          ????fmt.Printf("%s?%s\n",?*firstName,?*lastName)
          ????fmt.Println("returned?normally?from?fullName")
          }

          func?main()?{
          ????defer?fmt.Println("deferred?call?in?main")
          ????firstName?:=?"Elon"
          ????fullName(&firstName,?nil)
          ????fmt.Println("returned?normally?from?main")
          }

          在 playground 上運(yùn)行[8]

          上述代碼中,我們只修改了兩處,分別在第 8 行和第 20 行添加了延遲函數(shù)的調(diào)用。

          該函數(shù)會(huì)打印:

          This?program?prints,

          deferred?call?in?fullName
          deferred?call?in?main
          panic:?runtime?error:?last?name?cannot?be?nil

          goroutine?1?[running]:
          main.fullName(0x1042bf90,?0x0)
          ????/tmp/sandbox060731990/main.go:13?+0x280
          main.main()
          ????/tmp/sandbox060731990/main.go:22?+0xc0

          當(dāng)程序在第 13 行發(fā)生 panic 時(shí),首先執(zhí)行了延遲函數(shù),接著控制返回到函數(shù)調(diào)用方,調(diào)用方的延遲函數(shù)繼續(xù)運(yùn)行,直到到達(dá)頂層調(diào)用函數(shù)。

          在我們的例子中,首先執(zhí)行 fullName 函數(shù)中的 defer 語(yǔ)句(第 8 行)。程序打印出:

          deferred?call?in?fullName

          接著程序返回到 main 函數(shù),執(zhí)行了 main 函數(shù)的延遲調(diào)用,因此會(huì)輸出:

          deferred?call?in?main

          現(xiàn)在程序控制到達(dá)了頂層函數(shù),因此該函數(shù)會(huì)打印出 panic 信息,然后是堆棧跟蹤,最后終止程序。

          recover

          recover 是一個(gè)內(nèi)建函數(shù),用于重新獲得 panic 協(xié)程的控制。

          recover 函數(shù)的標(biāo)簽如下所示:

          func?recover()?interface{}

          只有在延遲函數(shù)的內(nèi)部,調(diào)用 recover 才有用。在延遲函數(shù)內(nèi)調(diào)用 recover,可以取到 panic 的錯(cuò)誤信息,并且停止 panic 續(xù)發(fā)事件(Panicking Sequence),程序運(yùn)行恢復(fù)正常。如果在延遲函數(shù)的外部調(diào)用 recover,就不能停止 panic 續(xù)發(fā)事件。

          我們來(lái)修改一下程序,在發(fā)生 panic 之后,使用 recover 來(lái)恢復(fù)正常的運(yùn)行。

          package?main

          import?(
          ????"fmt"
          )

          func?recoverName()?{
          ????if?r?:=?recover();?r!=?nil?{
          ????????fmt.Println("recovered?from?",?r)
          ????}
          }

          func?fullName(firstName?*string,?lastName?*string)?{
          ????defer?recoverName()
          ????if?firstName?==?nil?{
          ????????panic("runtime?error:?first?name?cannot?be?nil")
          ????}
          ????if?lastName?==?nil?{
          ????????panic("runtime?error:?last?name?cannot?be?nil")
          ????}
          ????fmt.Printf("%s?%s\n",?*firstName,?*lastName)
          ????fmt.Println("returned?normally?from?fullName")
          }

          func?main()?{
          ????defer?fmt.Println("deferred?call?in?main")
          ????firstName?:=?"Elon"
          ????fullName(&firstName,?nil)
          ????fmt.Println("returned?normally?from?main")
          }

          在 playground 上運(yùn)行[9]

          在第 7 行,recoverName() 函數(shù)調(diào)用了 recover(),返回了調(diào)用 panic 的傳參。在這里,我們只是打印出 recover 的返回值(第 8 行)。在 fullName 函數(shù)內(nèi),我們?cè)诘?14 行延遲調(diào)用了 recoverNames()

          當(dāng) fullName 發(fā)生 panic 時(shí),會(huì)調(diào)用延遲函數(shù) recoverName(),它使用了 recover() 來(lái)停止 panic 續(xù)發(fā)事件。

          該程序會(huì)輸出:

          recovered?from??runtime?error:?last?name?cannot?be?nil
          returned?normally?from?main
          deferred?call?in?main

          當(dāng)程序在第 19 行發(fā)生 panic 時(shí),會(huì)調(diào)用延遲函數(shù) recoverName,它反過(guò)來(lái)會(huì)調(diào)用 recover() 來(lái)重新獲得 panic 協(xié)程的控制。第 8 行調(diào)用了 recover,返回了 panic 的傳參,因此會(huì)打印:

          recovered?from??runtime?error:?last?name?cannot?be?nil

          在執(zhí)行完 recover() 之后,panic 會(huì)停止,程序控制返回到調(diào)用方(在這里就是 main 函數(shù)),程序在發(fā)生 panic 之后,從第 29 行開(kāi)始會(huì)繼續(xù)正常地運(yùn)行。程序會(huì)打印 returned normally from main,之后是 deferred call in main

          panic,recover 和 Go 協(xié)程

          只有在相同的 Go 協(xié)程[10]中調(diào)用 recover 才管用。recover 不能恢復(fù)一個(gè)不同協(xié)程的 panic。我們用一個(gè)例子來(lái)理解這一點(diǎn)。

          package?main

          import?(
          ????"fmt"
          ????"time"
          )

          func?recovery()?{
          ????if?r?:=?recover();?r?!=?nil?{
          ????????fmt.Println("recovered:",?r)
          ????}
          }

          func?a()?{
          ????defer?recovery()
          ????fmt.Println("Inside?A")
          ????go?b()
          ????time.Sleep(1?*?time.Second)
          }

          func?b()?{
          ????fmt.Println("Inside?B")
          ????panic("oh!?B?panicked")
          }

          func?main()?{
          ????a()
          ????fmt.Println("normally?returned?from?main")
          }

          在 playground 上運(yùn)行[11]

          在上面的程序中,函數(shù) b() 在第 23 行發(fā)生 panic。函數(shù) a() 調(diào)用了一個(gè)延遲函數(shù) recovery(),用于恢復(fù) panic。在第 17 行,函數(shù) b() 作為一個(gè)不同的協(xié)程來(lái)調(diào)用。下一行的 Sleep 只是保證 a()b() 運(yùn)行結(jié)束之后才退出。

          你認(rèn)為程序會(huì)輸出什么?panic 能夠恢復(fù)嗎?答案是否定的,panic 并不會(huì)恢復(fù)。因?yàn)檎{(diào)用 recovery 的協(xié)程和 b() 中發(fā)生 panic 的協(xié)程并不相同,因此不可能恢復(fù) panic。

          運(yùn)行該程序會(huì)輸出:

          Inside?A
          Inside?B
          panic:?oh!?B?panicked

          goroutine?5?[running]:
          main.b()
          ????/tmp/sandbox388039916/main.go:23?+0x80
          created?by?main.a
          ????/tmp/sandbox388039916/main.go:17?+0xc0

          從輸出可以看出,panic 沒(méi)有恢復(fù)。

          如果函數(shù) b() 在相同的協(xié)程里調(diào)用,panic 就可以恢復(fù)。

          如果程序的第 17 行由 go b() 修改為 b(),就可以恢復(fù) panic 了,因?yàn)?panic 發(fā)生在與 recover 相同的協(xié)程里。如果運(yùn)行這個(gè)修改后的程序,會(huì)輸出:

          Inside?A
          Inside?B
          recovered:?oh!?B?panicked
          normally?returned?from?main

          運(yùn)行時(shí) panic

          運(yùn)行時(shí)錯(cuò)誤(如數(shù)組越界)也會(huì)導(dǎo)致 panic。這等價(jià)于調(diào)用了內(nèi)置函數(shù) panic,其參數(shù)由接口類(lèi)型 runtime.Error[12] 給出。runtime.Error 接口的定義如下:

          type?Error?interface?{
          ????error
          ????//?RuntimeError?is?a?no-op?function?but
          ????//?serves?to?distinguish?types?that?are?run?time
          ????//?errors?from?ordinary?errors:?a?type?is?a
          ????//?run?time?error?if?it?has?a?RuntimeError?method.
          ????RuntimeError()
          }

          runtime.Error 接口滿(mǎn)足內(nèi)建接口類(lèi)型 `error`[13]

          我們來(lái)編寫(xiě)一個(gè)示例,創(chuàng)建一個(gè)運(yùn)行時(shí) panic。

          package?main

          import?(
          ????"fmt"
          )

          func?a()?{
          ????n?:=?[]int{5,?7,?4}
          ????fmt.Println(n[3])
          ????fmt.Println("normally?returned?from?a")
          }
          func?main()?{
          ????a()
          ????fmt.Println("normally?returned?from?main")
          }

          在 playground 上運(yùn)行[14]

          在上面的程序中,第 9 行我們?cè)噲D訪(fǎng)問(wèn) n[3],這是一個(gè)對(duì)切片[15]的錯(cuò)誤引用。該程序會(huì)發(fā)生 panic,輸出如下:

          panic:?runtime?error:?index?out?of?range

          goroutine?1?[running]:
          main.a()
          ????/tmp/sandbox780439659/main.go:9?+0x40
          main.main()
          ????/tmp/sandbox780439659/main.go:13?+0x20

          你也許想知道,是否可以恢復(fù)一個(gè)運(yùn)行時(shí) panic?當(dāng)然可以!我們來(lái)修改一下上面的代碼,恢復(fù)這個(gè) panic。

          package?main

          import?(
          ????"fmt"
          )

          func?r()?{
          ????if?r?:=?recover();?r?!=?nil?{
          ????????fmt.Println("Recovered",?r)
          ????}
          }

          func?a()?{
          ????defer?r()
          ????n?:=?[]int{5,?7,?4}
          ????fmt.Println(n[3])
          ????fmt.Println("normally?returned?from?a")
          }

          func?main()?{
          ????a()
          ????fmt.Println("normally?returned?from?main")
          }

          在 playground 上運(yùn)行[16]

          運(yùn)行上面程序會(huì)輸出:

          Recovered?runtime?error:?index?out?of?range
          normally?returned?from?main

          從輸出可以知道,我們已經(jīng)恢復(fù)了這個(gè) panic。

          恢復(fù)后獲得堆棧跟蹤

          當(dāng)我們恢復(fù) panic 時(shí),我們就釋放了它的堆棧跟蹤。實(shí)際上,在上述程序里,恢復(fù) panic 之后,我們就失去了堆棧跟蹤。

          有辦法可以打印出堆棧跟蹤,就是使用 `Debug`[17] 包中的 `PrintStack`[18] 函數(shù)。

          package?main

          import?(
          ????"fmt"
          ????"runtime/debug"
          )

          func?r()?{
          ????if?r?:=?recover();?r?!=?nil?{
          ????????fmt.Println("Recovered",?r)
          ????????debug.PrintStack()
          ????}
          }

          func?a()?{
          ????defer?r()
          ????n?:=?[]int{5,?7,?4}
          ????fmt.Println(n[3])
          ????fmt.Println("normally?returned?from?a")
          }

          func?main()?{
          ????a()
          ????fmt.Println("normally?returned?from?main")
          }

          在 playground 上運(yùn)行[19]

          在上面的程序中,我們?cè)诘?11 行使用了 debug.PrintStack() 打印堆棧跟蹤。

          該程序會(huì)輸出:

          Recovered?runtime?error:?index?out?of?range
          goroutine?1?[running]:
          runtime/debug.Stack(0x1042beb8,?0x2,?0x2,?0x1c)
          ????/usr/local/go/src/runtime/debug/stack.go:24?+0xc0
          runtime/debug.PrintStack()
          ????/usr/local/go/src/runtime/debug/stack.go:16?+0x20
          main.r()
          ????/tmp/sandbox949178097/main.go:11?+0xe0
          panic(0xf0a80,?0x17cd50)
          ????/usr/local/go/src/runtime/panic.go:491?+0x2c0
          main.a()
          ????/tmp/sandbox949178097/main.go:18?+0x80
          main.main()
          ????/tmp/sandbox949178097/main.go:23?+0x20
          normally?returned?from?main

          從輸出我們可以看出,首先已經(jīng)恢復(fù)了 panic,打印出 Recovered runtime error: index out of range。此外,我們也打印出了堆棧跟蹤。在恢復(fù)了 panic 之后,還打印出 normally returned from main

          本教程到此結(jié)束。

          簡(jiǎn)單概括一下本教程討論的內(nèi)容:

          • 什么是 panic?
          • 什么時(shí)候應(yīng)該使用 panic?
          • panic 示例
          • 發(fā)生 panic 時(shí)的 defer
          • recover
          • panic,recover 和 Go 協(xié)程
          • 運(yùn)行時(shí) panic
          • 恢復(fù)后獲得堆棧跟蹤

          祝你愉快。

          上一教程 - 自定義錯(cuò)誤

          下一教程 - 函數(shù)是一等公民


          via: https://golangbot.com/panic-and-recover/

          作者:Nick Coghlan[20]譯者:Noluye[21]校對(duì):polaris1119[22]

          本文由 GCTT[23] 原創(chuàng)編譯,Go 中文網(wǎng)[24] 榮譽(yù)推出

          參考資料

          [1]

          Golang 系列教程: https://studygolang.com/subject/2

          [2]

          錯(cuò)誤: https://studygolang.com/articles/12724

          [3]

          函數(shù): https://studygolang.com/articles/11892

          [4]

          延遲: https://studygolang.com/articles/12719

          [5]

          協(xié)程: https://studygolang.com/articles/12342

          [6]

          錯(cuò)誤: https://studygolang.com/articles/12724

          [7]

          在 playground 上運(yùn)行: https://play.golang.org/p/xQJYRSCu8S

          [8]

          在 playground 上運(yùn)行: https://play.golang.org/p/oUFnu-uTmC

          [9]

          在 playground 上運(yùn)行: https://play.golang.org/p/I9pp8N55c1

          [10]

          Go 協(xié)程: https://studygolang.com/articles/12342

          [11]

          在 playground 上運(yùn)行: https://play.golang.org/p/pEVzTLz36Y

          [12]

          runtime.Error: https://golang.org/src/runtime/error.go?s=267:503#L1

          [13]

          error: https://golangbot.com/error-handling/#errortyperepresentation

          [14]

          在 playground 上運(yùn)行: https://play.golang.org/p/CBsK2xXzGg

          [15]

          3]`,這是一個(gè)對(duì)[切片: https://studygolang.com/articles/12121

          [16]

          在 playground 上運(yùn)行: https://play.golang.org/p/qusvZe5rft

          [17]

          Debug: https://golang.org/pkg/runtime/debug/

          [18]

          PrintStack: https://golang.org/pkg/runtime/debug/#PrintStack

          [19]

          在 playground 上運(yùn)行: https://play.golang.org/p/D-QlDmumHV

          [20]

          Nick Coghlan: https://golangbot.com/about/

          [21]

          Noluye: https://github.com/Noluye

          [22]

          polaris1119: https://github.com/polaris1119

          [23]

          GCTT: https://github.com/studygolang/GCTT

          [24]

          Go 中文網(wǎng): https://studygolang.com/



          推薦閱讀


          福利

          我為大家整理了一份從入門(mén)到進(jìn)階的Go學(xué)習(xí)資料禮包,包含學(xué)習(xí)建議:入門(mén)看什么,進(jìn)階看什么。關(guān)注公眾號(hào) 「polarisxu」,回復(fù) ebook 獲取;還可以回復(fù)「進(jìn)群」,和數(shù)萬(wàn) Gopher 交流學(xué)習(xí)。

          瀏覽 30
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  一级免费试看 | 久久性爱视屏 | 青娱乐自拍视频地址 | 精品人妻网站 | 色屁屁TS人妖系列二区 |