亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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í)候,心里常常很多疑問,有時(shí)候看似懂了的問題,其實(shí)是是而非。

panic 究竟是啥?看似顯而易見的問題,但是卻回答不出個(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

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

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

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

奇伢說的是匯編代碼。因?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í)行到這里,就說明 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ā)

最典型的是非法地址訪問,比如, nil 指針 訪問會(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

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

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

在 cpu 訪問到 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 類似,都是對(duì) panic( ) 的極簡(jiǎn)封裝:

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

劃重點(diǎn):這種方式是通過信號(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)說,這個(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)在回到最本源的問題: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í)行完了都還沒有恢復(fù) _panic 的狀態(tà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è)小問題:

  • panic 究竟是啥?是一個(gè)結(jié)構(gòu)體?還是一個(gè)函數(shù)?
  • 為什么 panic 會(huì)讓 Go 進(jìn)程退出的 ?
  • 為什么 recover 一定要放在 defer 里面才生效?
  • 為什么 recover 已經(jīng)放在 defer 里面,但是進(jìn)程還是沒有恢復(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中強(qiáng)大的重試機(jī)制

    一文帶你了解Golang中強(qiáng)大的重試機(jī)制

    在 Go 語(yǔ)言中,處理瞬態(tài)錯(cuò)誤是常見的挑戰(zhàn),這些錯(cuò)誤可能會(huì)在一段時(shí)間后自動(dòng)恢復(fù),因此,重試機(jī)制在這些情況下非常重要,所以本文就來(lái)和大家聊聊Golang中強(qiáng)大的重試機(jī)制吧
    2025-01-01
  • 自動(dòng)生成代碼controller?tool的簡(jiǎn)單使用

    自動(dòng)生成代碼controller?tool的簡(jiǎn)單使用

    這篇文章主要為大家介紹了自動(dòng)生成代碼controller?tool的簡(jiǎn)單使用示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-05-05
  • go語(yǔ)言LeetCode題解944刪列造序示例詳解

    go語(yǔ)言LeetCode題解944刪列造序示例詳解

    這篇文章主要為大家介紹了go語(yǔ)言LeetCode題解944刪列造序示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-12-12
  • go語(yǔ)言工程結(jié)構(gòu)

    go語(yǔ)言工程結(jié)構(gòu)

    這篇文章主要簡(jiǎn)單介紹了go語(yǔ)言工程結(jié)構(gòu),對(duì)于我們學(xué)習(xí)go語(yǔ)言很有幫助,需要的朋友可以參考下
    2015-01-01
  • 利用GO語(yǔ)言實(shí)現(xiàn)多人聊天室實(shí)例教程

    利用GO語(yǔ)言實(shí)現(xiàn)多人聊天室實(shí)例教程

    聊天室的實(shí)現(xiàn)大家應(yīng)該都遇到過,這篇文章主要給大家介紹了關(guān)于利用GO語(yǔ)言實(shí)現(xiàn)多人聊天室的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起看看吧。
    2018-03-03
  • 使用Go語(yǔ)言編寫一個(gè)NTP服務(wù)器的流程步驟

    使用Go語(yǔ)言編寫一個(gè)NTP服務(wù)器的流程步驟

    NTP服務(wù)器【Network?Time?Protocol(NTP)】是用來(lái)使計(jì)算機(jī)時(shí)間同步化的一種協(xié)議,為了確保封閉局域網(wǎng)內(nèi)多個(gè)服務(wù)器的時(shí)間同步,我們計(jì)劃部署一個(gè)網(wǎng)絡(luò)時(shí)間同步服務(wù)器(NTP服務(wù)器),本文給大家介紹了使用Go語(yǔ)言編寫一個(gè)NTP服務(wù)器的流程步驟,需要的朋友可以參考下
    2024-11-11
  • Golang中指針的使用詳解

    Golang中指針的使用詳解

    Golang是一門支持指針的編程語(yǔ)言,指針是一種特殊的變量,存儲(chǔ)了其他變量的地址。通過指針,可以在程序中直接訪問和修改變量的值,避免了不必要的內(nèi)存拷貝和傳遞。Golang中的指針具有高效、安全的特點(diǎn),在并發(fā)編程和底層系統(tǒng)開發(fā)中得到廣泛應(yīng)用
    2023-04-04
  • 詳解Go語(yǔ)言如何使用xorm實(shí)現(xiàn)讀取mysql

    詳解Go語(yǔ)言如何使用xorm實(shí)現(xiàn)讀取mysql

    xorm是go語(yǔ)言的常用orm之一,可以用來(lái)操作數(shù)據(jù)庫(kù)。本文就來(lái)和大家聊聊Go語(yǔ)言如何使用xorm實(shí)現(xiàn)讀取mysql功能,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2022-11-11
  • golang return省略用法說明

    golang return省略用法說明

    這篇文章主要介紹了golang return省略用法說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來(lái)看看吧
    2020-12-12
  • 利用go-zero在Go中快速實(shí)現(xiàn)JWT認(rèn)證的步驟詳解

    利用go-zero在Go中快速實(shí)現(xiàn)JWT認(rèn)證的步驟詳解

    這篇文章主要介紹了如何利用go-zero在Go中快速實(shí)現(xiàn)JWT認(rèn)證,本文分步驟通過實(shí)例圖文相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2020-10-10

最新評(píng)論