最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Go?panic的三種產(chǎn)生方式細(xì)節(jié)探究

 更新時(shí)間:2023年12月01日 10:12:46   作者:qiya  
這篇文章主要介紹了Go?panic的三種產(chǎn)生方式細(xì)節(jié)探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

為什么 panic 值得思考?

初學(xué) Go 的時(shí)候,心里常常很多疑問(wèn),有時(shí)候看似懂了的問(wèn)題,其實(shí)是是而非。

panic 究竟是啥?看似顯而易見(jiàn)的問(wèn)題,但是卻回答不出個(gè)所以然來(lái)。奇伢分兩個(gè)章節(jié)來(lái)徹底搞懂 panic 的知識(shí):

  • 姿勢(shì)篇:摸清楚 panic 的誕生,它不是石頭里蹦出來(lái)的,總結(jié)有三種姿勢(shì);
  • 原理篇:徹底搞明白 panic 的內(nèi)部原理,理解 panic 的深層原理;

panic 的三種姿勢(shì)

什么時(shí)候會(huì)產(chǎn)生 panic ?

我們先從“形”來(lái)學(xué)習(xí)。從程序猿的角度來(lái)看,可以分為主動(dòng)和被動(dòng)方式,被動(dòng)的方式有兩種,如下:

主動(dòng)方式:

  • 程序猿主動(dòng)調(diào)用 panic( ) 函數(shù);

被動(dòng)的方式:

  • 編譯器的隱藏代碼觸發(fā);
  • 內(nèi)核發(fā)送給進(jìn)程信號(hào)觸發(fā) ;

編譯器的隱藏代碼

Go 之所以簡(jiǎn)單又強(qiáng)大,編譯器居功至偉。非常多的事情是編譯器幫程序猿做了的,邏輯補(bǔ)充,內(nèi)存的逃逸分析等等。

包括 panic 的拋出!

舉個(gè)非常典型的例子:整數(shù)算法除零會(huì)發(fā)生 panic,怎么做到的?

看一段極簡(jiǎn)代碼:

func divzero(a, b int) int {
    c := a/b
    return c
}

上面函數(shù)就會(huì)有除零的風(fēng)險(xiǎn),當(dāng) b 等于 0 的時(shí)候,程序就會(huì)觸發(fā) panic,然后退出,如下:

root@ubuntu:~/code/gopher/src/panic# ./test_zero 
panic: runtime error: integer divide by zero
goroutine 1 [running]:
main.zero(0x64, 0x0, 0x0)
 /root/code/gopher/src/panic/test_zero.go:6 +0x52

問(wèn)題來(lái)了:程序怎么觸發(fā)的 panic ?

代碼面前無(wú)秘密。

可代碼看不出啥呀,不就是一行 c := a/b 嘛?

奇伢說(shuō)的是匯編代碼。因?yàn)檫@段隱藏起來(lái)的邏輯,是編譯器幫你加的。

用 dlv 調(diào)試斷點(diǎn)到 divzero 函數(shù),然后執(zhí)行 disassemble ,你就能看到秘密了。奇伢截取部分匯編,并備注了下:

(dlv) disassemble
TEXT main.zero(SB) /root/code/gopher/src/panic/test_zero.go

    // 判斷 b 是否等于 0 
    test_zero.go:6  0x4aa3c1    4885c9          test rcx, rcx
    // 不等于 0 就跳轉(zhuǎn)到 0x4aa3c8 執(zhí)行指令,否則就往下執(zhí)行
    test_zero.go:6  0x4aa3c4    7502            jnz 0x4aa3c8
    // 執(zhí)行到這里,就說(shuō)明 b 是 0 值,就跳轉(zhuǎn)到 0x4aa3ed ,也就是 call $runtime.panicdivide
=>  test_zero.go:6  0x4aa3c6    eb25            jmp 0x4aa3ed
    test_zero.go:6  0x4aa3c8    4883f9ff        cmp rcx, -0x1
    test_zero.go:6  0x4aa3cc    7407            jz 0x4aa3d5
    test_zero.go:6  0x4aa3ce    4899            cqo
    test_zero.go:6  0x4aa3d0    48f7f9          idiv rcx
    // ...
    test_zero.go:7  0x4aa3ec    c3              ret
    // 看到神奇的函數(shù)了嘛 !
    test_zero.go:6  0x4aa3ed    e8ee27f8ff      call $runtime.panicdivide

看到秘密的函數(shù)了嗎?

編譯器偷偷加上了一段 if/else 的判斷邏輯,并且還給加了 runtime.panicdivide 的代碼。

  • 如果 b == 0 ,那么跳轉(zhuǎn)執(zhí)行函數(shù) runtime.panicdivide ;

再來(lái)看一眼 panicdivide 函數(shù),這是一段極簡(jiǎn)的封裝:

// runtime/panic.go
func panicdivide() {
    panicCheck2("integer divide by zero")
    panic(divideError)
}

看到了不,這里面調(diào)用的就是 panic() 函數(shù)。

除零觸發(fā)的 panic 就是這樣來(lái)的,它不是石頭里蹦出來(lái)的,而是編譯器多加的邏輯判斷保證了除數(shù)為 0 的時(shí)候,觸發(fā) panic 函數(shù)。

劃重點(diǎn):編譯器加的隱藏邏輯,調(diào)用了拋出 panic 的函數(shù)。Go 的編譯器才是真大佬!

進(jìn)程信號(hào)觸發(fā)

最典型的是非法地址訪問(wèn),比如, nil 指針 訪問(wèn)會(huì)觸發(fā) panic,怎么做到的?

看一個(gè)極簡(jiǎn)的例子:

func nilptr(b *int) int {
    c := *b
    return c
}

當(dāng)調(diào)用 nilptr( nil ) 的時(shí)候,將會(huì)導(dǎo)致進(jìn)程異常退出:

root@ubuntu:~/code/gopher/src/panic# ./test_nil 

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x4aa3bc]

goroutine 1 [running]:
main.nilptr(0x0, 0x0)
 /root/code/gopher/src/panic/test_nil.go:6 +0x1c

問(wèn)題來(lái)了:這里的 panic 又是怎么形成的呢?

在 Go 進(jìn)程啟動(dòng)的時(shí)候會(huì)注冊(cè)默認(rèn)的信號(hào)處理程序( sigtramp )

在 cpu 訪問(wèn)到 0 地址會(huì)觸發(fā) page fault 異常,這是一個(gè)非法地址,內(nèi)核會(huì)發(fā)送 SIGSEGV 信號(hào)給進(jìn)程,所以當(dāng)收到 SIGSEGV 信號(hào)的時(shí)候,就會(huì)讓 sigtramp 函數(shù)來(lái)處理,最終調(diào)用到 panic 函數(shù) :

// 信號(hào)處理函數(shù)回調(diào)
sigtramp (純匯編代碼)
    -> sigtrampgo ( signal_unix.go )
        -> sighandler  ( signal_sighandler.go )
            -> preparePanic ( signal_amd64x.go )
                -> sigpanic ( signal_unix.go )
                    -> panicmem 
                        -> panic (內(nèi)存段錯(cuò)誤)

在 sigpanic 函數(shù)中會(huì)調(diào)用到 panicmem ,在這個(gè)里面就會(huì)調(diào)用 panic 函數(shù),從而走上了 Go 自己的 panic 之路。

panicmem 和 panicdivide 類(lèi)似,都是對(duì) panic( ) 的極簡(jiǎn)封裝:

func panicmem() {
    panicCheck2("invalid memory address or nil pointer dereference")
    panic(memoryError)
}

劃重點(diǎn):這種方式是通過(guò)信號(hào)軟中斷的方式來(lái)走到 Go 注冊(cè)的信號(hào)處理邏輯,從而調(diào)用到 panic( ) 的函數(shù)。

童鞋可能會(huì)好奇,信號(hào)處理的邏輯什么時(shí)候注冊(cè)進(jìn)去的?

在進(jìn)程初始化的時(shí)候,創(chuàng)建 M0(線程)的時(shí)候用系統(tǒng)調(diào)用 sigaction 給信號(hào)注冊(cè)處理函數(shù)為 sigtramp ,調(diào)用棧如下:

mstartm0 (proc.go)
    -> initsig (signal_unix.go:113)
        -> setsig (os_linux.go)

這樣的話,以后觸發(fā)了信號(hào)軟中斷,就能調(diào)用到 Go 的信號(hào)處理函數(shù),從而進(jìn)行語(yǔ)言層面的 panic 處理 。

總的來(lái)說(shuō),這個(gè)是從系統(tǒng)層面到特定語(yǔ)言層面的處理轉(zhuǎn)變。

程序猿主動(dòng)

第三種方式,就是程序猿自己主動(dòng)調(diào)用 panic 拋出來(lái)的。

func main() {
    panic("panic test")
}

簡(jiǎn)單的函數(shù)調(diào)用,這個(gè)超簡(jiǎn)單的。

聊聊 panic 到底是什么?

現(xiàn)在我們摸透了 panic 產(chǎn)生的姿勢(shì),以上三種方式,無(wú)論哪一種都?xì)w一到 panic( ) 這個(gè)函數(shù)調(diào)用。所以有一點(diǎn)很明確:panic 這個(gè)東西是語(yǔ)言層面的處理邏輯。

panic 發(fā)生之后,如果 Go 不做任何特殊處理,默認(rèn)行為是打印堆棧,退出程序。

現(xiàn)在回到最本源的問(wèn)題:panic 到底是什么?

這里不糾結(jié)概念,只描述幾個(gè)簡(jiǎn)單的事實(shí):

  • panic( ) 函數(shù)內(nèi)部會(huì)產(chǎn)生一個(gè)關(guān)鍵的數(shù)據(jù)結(jié)構(gòu)體 _panic ,并且掛接到 goroutine 之上;
  • panic( ) 函數(shù)內(nèi)部會(huì)執(zhí)行 _defer 函數(shù)鏈條,并針對(duì) _panic 的狀態(tài)進(jìn)行對(duì)應(yīng)的處理;

什么叫做 panic( ) 的對(duì)應(yīng)的處理?

循環(huán)執(zhí)行 goroutine 上面的 _defer 函數(shù)鏈,如果執(zhí)行完了都還沒(méi)有恢復(fù) _panic 的狀態(tài),那就沒(méi)得辦法了,退出進(jìn)程,打印堆棧。

如果在 goroutine 的 _defer 鏈上,有個(gè)朋友 recover 了一下,把這個(gè) _panic 標(biāo)記成恢復(fù),那事情就到此為止,就從這個(gè) _defer 函數(shù)執(zhí)行后續(xù)正常代碼即可,走 deferreturn 的邏輯。

所以,panic 是什么 ?

小奇伢認(rèn)為,它就是個(gè)特殊函數(shù)調(diào)用,僅此而已。

有多特殊?

限于篇幅,此處不表,下篇剖析其深度原理??梢韵人伎紟讉€(gè)小問(wèn)題:

  • panic 究竟是啥?是一個(gè)結(jié)構(gòu)體?還是一個(gè)函數(shù)?
  • 為什么 panic 會(huì)讓 Go 進(jìn)程退出的 ?
  • 為什么 recover 一定要放在 defer 里面才生效?
  • 為什么 recover 已經(jīng)放在 defer 里面,但是進(jìn)程還是沒(méi)有恢復(fù)?
  • 為什么 panic 之后,還能再 panic ?有啥影響?

總結(jié)

  • panic 產(chǎn)生的三大姿勢(shì):程序猿主動(dòng),編譯器輔助邏輯,軟中斷信號(hào)觸發(fā);
  • 無(wú)論哪一種姿勢(shì),最終都是歸一到 panic( ) 函數(shù)的處理,panic 只是語(yǔ)言層面的處理邏輯;
  • panic 發(fā)生之后,如果不做處理,默認(rèn)行為是打印 panic 原因,打印堆棧,進(jìn)程退出;

以上就是Go panic的三種產(chǎn)生方式細(xì)節(jié)探究的詳細(xì)內(nèi)容,更多關(guān)于Go panic產(chǎn)生方式的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • golang判斷key是否在map中的代碼

    golang判斷key是否在map中的代碼

    這篇文章主要介紹了golang判斷key是否在map中的代碼,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-04-04
  • Go?語(yǔ)言結(jié)構(gòu)體鏈表的基本操作

    Go?語(yǔ)言結(jié)構(gòu)體鏈表的基本操作

    鏈表是一種物理存儲(chǔ)單元上非連續(xù)、非順序的存儲(chǔ)結(jié)構(gòu),數(shù)據(jù)元素的邏輯順序是通過(guò)鏈表中的指針鏈接次序?qū)崿F(xiàn)的,這篇文章主要介紹了Go?語(yǔ)言結(jié)構(gòu)體鏈表,需要的朋友可以參考下
    2022-04-04
  • Go高性能緩沖IO中bufio包的使用小結(jié)

    Go高性能緩沖IO中bufio包的使用小結(jié)

    本文主要介紹了Go高性能緩沖IO中bufio包的使用小結(jié),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2026-04-04
  • 淺析Golang如何利用泛型編寫(xiě)更安全的代碼

    淺析Golang如何利用泛型編寫(xiě)更安全的代碼

    從Go 1.18正式引入泛型,再到Go 1.21大量泛型函數(shù)/類(lèi)型進(jìn)入標(biāo)準(zhǔn)庫(kù)開(kāi)始已經(jīng)過(guò)去了三年,這篇文章要說(shuō)的是泛型在強(qiáng)化代碼安全性和健壯性方面的應(yīng)用,感興趣的可以了解下
    2025-12-12
  • 使用Golang操作TMDb?API的使用小結(jié)

    使用Golang操作TMDb?API的使用小結(jié)

    本文主要介紹了使用Golang操作TMDb?API的使用小結(jié),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2026-05-05
  • 一文詳解go的defer和return的執(zhí)行順序

    一文詳解go的defer和return的執(zhí)行順序

    go的defer和return是golang中的兩個(gè)關(guān)鍵字,return用于返回函數(shù)的返回值,也可以參與一定的流程控制,defer是golang中的延遲調(diào)用,經(jīng)常用于文件流的關(guān)閉,鎖的解鎖操作,本文給大家介紹了go的defer和return的執(zhí)行順序,需要的朋友可以參考下
    2024-07-07
  • 關(guān)于go get 下載第三方包存儲(chǔ)路徑問(wèn)題

    關(guān)于go get 下載第三方包存儲(chǔ)路徑問(wèn)題

    這篇文章主要介紹了關(guān)于go get 下載第三方包存儲(chǔ)路徑問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-01-01
  • Golang線上內(nèi)存爆掉問(wèn)題排查(pprof)與解決

    Golang線上內(nèi)存爆掉問(wèn)題排查(pprof)與解決

    這篇文章主要介紹了Golang線上內(nèi)存爆掉問(wèn)題排查(pprof)與解決,涉及到數(shù)據(jù)敏感,文中代碼是我模擬線上故障的一個(gè)情況,好在我們程序都有添加pprof監(jiān)控,于是直接通過(guò)go tool pprof分析,需要的朋友可以參考下
    2024-04-04
  • golang?struct?json?tag的使用以及深入講解

    golang?struct?json?tag的使用以及深入講解

    這篇文章主要給大家介紹了關(guān)于golang?struct?json?tag的使用以及深入講解,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2022-02-02
  • 手把手帶你走進(jìn)Go語(yǔ)言之語(yǔ)法基礎(chǔ)解析

    手把手帶你走進(jìn)Go語(yǔ)言之語(yǔ)法基礎(chǔ)解析

    這篇文章主要介紹了手把手帶你走進(jìn)Go語(yǔ)言之語(yǔ)法基礎(chǔ),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-09-09

最新評(píng)論

红河县| 页游| 柘荣县| 梁山县| 泽州县| 崇信县| 英吉沙县| 屯门区| 慈溪市| 宁晋县| 当涂县| 行唐县| 河津市| 南开区| 乡宁县| 定兴县| 竹溪县| 武陟县| 集安市| 凤冈县| 康定县| 华容县| 遵义县| 上虞市| 遂平县| 余江县| 乌兰察布市| 始兴县| 华坪县| 贵南县| 九龙坡区| 泸定县| 昔阳县| 南陵县| 吕梁市| 潜江市| 类乌齐县| 麻栗坡县| 巍山| 桐柏县| 新津县|