panic與recover底層原理

panic與recover
有defer有panic, defer中沒有recover且沒有panic
有defer有panic, defer中有panic阻荒,但是沒有recover
有defer有panic, defer中有recover澄阳,但是沒有panic
有defer有panic, defer中同時(shí)有recover, panic
總結(jié)
有defer有panic, defer中沒有recover且沒有panic


image.png

我們已經(jīng)知道當(dāng)前執(zhí)行的goroutine中有一個(gè)defer鏈表的頭指針,其實(shí)它也有一個(gè)panic鏈表頭指針舆逃。panic鏈表連起來(lái)的,是一個(gè)一個(gè)_panic結(jié)構(gòu)體,和defer鏈表一樣肯骇,發(fā)送新的panic時(shí)圃泡,也是在鏈表頭上插入新的_panic結(jié)構(gòu)體碟案。所以鏈表頭上的panic,就是當(dāng)前正在執(zhí)行的那一個(gè)颇蜡。


image.png

來(lái)看個(gè)例子价说,這里函數(shù)A注冊(cè)了兩個(gè)defer函數(shù)A1和A2后發(fā)生panic, 執(zhí)行到panic后风秤,panic后面的代碼就不會(huì)執(zhí)行了鳖目,而是進(jìn)入panic處理邏輯,首先在panic鏈表中增加一項(xiàng)缤弦,記為panicA领迈,它就是當(dāng)前正在執(zhí)行的panic,然后就該執(zhí)行defer鏈表了。


image.png

從頭開始執(zhí)行狸捅,不過與函數(shù)正常執(zhí)行defer有些不同衷蜓,panic執(zhí)行defer時(shí),會(huì)先把它的started置為true尘喝,標(biāo)記它已經(jīng)開始執(zhí)行磁浇,并且會(huì)把_panic字段指向當(dāng)前執(zhí)行的panic。表示這個(gè)defer是由這個(gè)panic觸發(fā)的朽褪。


image.png

回到例子中置吓,A2執(zhí)行前也要先標(biāo)記,如果函數(shù)A2能正常結(jié)束缔赠,這一項(xiàng)defer就會(huì)被移除衍锚,繼續(xù)執(zhí)行下個(gè)defer。之所有這樣設(shè)計(jì)橡淑,是為了應(yīng)對(duì)defer函數(shù)沒有正常結(jié)束的情況构拳。


image.png

有defer有panic, defer中有panic,但是沒有recover

例如接下來(lái)要執(zhí)行的defer函數(shù)A1中梁棠,會(huì)再次發(fā)生panic置森,執(zhí)行前同樣標(biāo)記它的started和_panic字段,當(dāng)A1執(zhí)行到這里時(shí)再次發(fā)生panic符糊,這后面的代碼也不會(huì)執(zhí)行了凫海,然后在panic鏈表頭插入一個(gè)新的panic,


image.png

現(xiàn)在它成為當(dāng)前執(zhí)行的panic了男娄。然后同樣去執(zhí)行defer鏈表行贪,但是發(fā)現(xiàn)A1已經(jīng)執(zhí)行,并且觸發(fā)它執(zhí)行的并不是當(dāng)前的panicA1模闲。所以要根據(jù)這里記錄的panic指針建瘫,找到對(duì)應(yīng)的panic,并把它標(biāo)記為已終止尸折。

image.png

是時(shí)候把_panic結(jié)構(gòu)體展開了啰脚,第一個(gè)字段存儲(chǔ)panic當(dāng)前執(zhí)行的defer的函數(shù)參數(shù)地址,下面這個(gè)字段就是panic函數(shù)自己的參數(shù)了实夹,link鏈到之前發(fā)生的panic橄浓,recovered表示panic是否被恢復(fù),aborted表示panic是否被終止亮航。所以回到例子中荸实,panicA被終止。


image.png

這里不僅要把panicA標(biāo)識(shí)為已終止缴淋,還要把deferA1這一項(xiàng)移除


image.png

接下來(lái)就該打印panic信息了准给,注意panic打印異常信息時(shí)泄朴,會(huì)從鏈表尾開始,也就是按照panic發(fā)生的順序逐個(gè)輸出圆存,所以這里會(huì)先輸出panicA叼旋,然后是panicA1,沒有發(fā)生recover時(shí)panic的處理邏輯就是這樣沦辙。
關(guān)鍵點(diǎn)有兩個(gè):

第一個(gè)是panic執(zhí)行defer函數(shù)的方式,先標(biāo)記后釋放讹剔,目的是為了終止之前發(fā)生的panic油讯。
第二個(gè)是異常信息的輸出方式,所有還在panic鏈表上的項(xiàng)都會(huì)被輸出延欠,順序與panic發(fā)生的順序一致
有defer有panic, defer中有recover陌兑,但是沒有panic


image.png

接下來(lái)增加recover看看, recover本身的邏輯很簡(jiǎn)單由捎,它只做一件事兔综,就是把當(dāng)前執(zhí)行的panic置為已恢復(fù),也就是把它的recovered字段置為true狞玛,其他的都不管软驰。


image.png

函數(shù)A里有兩個(gè)defer函數(shù),并且會(huì)發(fā)生panic心肪,而defer函數(shù)A2中會(huì)執(zhí)行recover锭亏,panic發(fā)生時(shí),當(dāng)前goroutine中defer鏈表只有A1和A2硬鞍,panic鏈表有一項(xiàng)慧瘤,標(biāo)記為panicA。panicA觸發(fā)defer執(zhí)行固该,先執(zhí)行函數(shù)A2锅减,執(zhí)行到第一行發(fā)生recover,把當(dāng)前執(zhí)行的panicA置為已恢復(fù)伐坏,然后recover函數(shù)的任務(wù)就完成了怔匣。程序繼續(xù)往下走,直到A2結(jié)束著淆。


image.png

其實(shí)在每個(gè)defer執(zhí)行完之后劫狠,panic處理流程都會(huì)檢查當(dāng)前panic是否被它恢復(fù)了。此時(shí)發(fā)現(xiàn)panicA已經(jīng)被恢復(fù)永部,就會(huì)把它從鏈表中移除独泞,A2這一項(xiàng)也會(huì)從defer鏈表中移除 。不過在移除前苔埋,要保存_defer.sp和_defer.pc這兩個(gè)字段的值懦砂,接下來(lái)要做的就是利用保存的sp和pc跳出panicA的處理流程。


image.png

但是要怎么跳出來(lái)呢?又恢復(fù)到哪里去呢荞膘?我們知道罚随,sp和pc是注冊(cè)defer函數(shù)時(shí)保存的,這里sp就是函數(shù)A的棧指針羽资,而pc字段淘菩,就是調(diào)用deferproc函數(shù)的返回地址(下一條指令的地址) ,這就是這段偽指令中判斷返回值是否大于零的這部分邏輯屠升。


image.png

通過sp可以恢復(fù)到函數(shù)A的棧幀潮改,通過pc可以恢復(fù)到這里的指令地址,但是r就不能是0了腹暖,否則函數(shù)A就會(huì)重復(fù)執(zhí)行汇在,我們之前提過,這個(gè)返回值被編譯器保存在一個(gè)寄存器中脏答,所以只要把它置為1糕殉,就可以執(zhí)行g(shù)oto ret,跳轉(zhuǎn)到deferreturn這里殖告,繼續(xù)執(zhí)行defer鏈表了 阿蝶。


image.png

不過要注意的是,deferreturn只負(fù)責(zé)執(zhí)行當(dāng)前函數(shù)A注冊(cè)的defer函數(shù)丛肮,它是通過棧指針來(lái)判斷的赡磅,defer鏈表中接下來(lái)要執(zhí)行的A1 ,也是函數(shù)A注冊(cè)的宝与,所以函數(shù)A1執(zhí)行焚廊,A1結(jié)束后defer鏈表為空,函數(shù)A結(jié)束习劫。


image.png

這就是recover的基本處理流程咆瘟。雖然recover函數(shù)只設(shè)置了當(dāng)前panic的一個(gè)屬性,但是會(huì)引發(fā)panic處理流程诽里,移除被恢復(fù)的panic并跳出當(dāng)前panic的處理流程袒餐。但是要注意,在發(fā)生recover的函數(shù)正常返回以后谤狡,才會(huì)進(jìn)入到檢測(cè)panic是否被恢復(fù)的流程灸眼,然后才能刪除被恢復(fù)的panic。


image.png

有defer有panic, defer中同時(shí)有recover, panic

如果發(fā)生recover的defer函數(shù)在返回之前又發(fā)生了panic墓懂,情況就又不一樣了了焰宣。我們先恢復(fù)到recover發(fā)生時(shí)defer鏈表和panic鏈表的狀態(tài)。執(zhí)行到這里再次panic時(shí)捕仔,panic鏈表增加一項(xiàng)匕积。記為panicA2盈罐,它成為當(dāng)前panic,并且執(zhí)行defer鏈表闪唆,發(fā)現(xiàn)A2已經(jīng)由之前的panicA執(zhí)行了盅粪,所以把panicA終止,并把A2從defer鏈表中移除悄蕾,繼續(xù)執(zhí)行下一個(gè)票顾,A1就是由panicA2觸發(fā)執(zhí)行的了。

image.png

A1結(jié)束后被移除帆调,defer鏈表為空库物,接下來(lái)就要輸出異常現(xiàn)象了贷帮,注意,對(duì)于panic鏈表中已經(jīng)被恢復(fù)的panic诱告,打印它的信息時(shí)撵枢,會(huì)加上recovered標(biāo)記。panic鏈表每一項(xiàng)都輸出后精居,程序退出锄禽。這個(gè)例子主要是幫助我們理解,被恢復(fù)的panic在什么情況下不會(huì)被移除靴姿。

image.png

下面我們?cè)倏匆粋€(gè)例子沃但,這一次我們結(jié)合函數(shù)調(diào)用關(guān)系來(lái)弄清楚recover發(fā)生后,程序究竟會(huì)恢復(fù)到哪里佛吓,這里函數(shù)A注冊(cè)了兩個(gè)defer函數(shù)后發(fā)生panic宵晚,defer函數(shù)A1可以正常執(zhí)行,但是defer函數(shù)A2注冊(cè)了defer函數(shù)B1后再次panic维雇,而defer函數(shù)B1會(huì)發(fā)生recover淤刃。

image.png

首先注冊(cè)兩個(gè)defer函數(shù)A1和A2,發(fā)生panic時(shí)吱型,實(shí)際上會(huì)調(diào)用runtime.gopanic函數(shù)逸贾,它負(fù)責(zé)添加panic鏈表項(xiàng),并執(zhí)行defer鏈表津滞。panicA首先會(huì)執(zhí)行defer函數(shù)A2铝侵,A2會(huì)注冊(cè)一個(gè)defer函數(shù)B1,然后再次發(fā)生panic触徐,所以函數(shù)A2也會(huì)調(diào)用gopanic函數(shù)咪鲜。panicA2也去執(zhí)行defer鏈表。


image.png

首先是B1锌介,B1執(zhí)行時(shí)調(diào)用recover函數(shù)嗜诀,把panicA2置為已恢復(fù)猾警。


image.png

B1正常結(jié)束后,返回到panicA2的處理流程隆敢,檢測(cè)到panicA2已恢復(fù)发皿,把它從鏈表中移除 ,因?yàn)閐efer函數(shù)B1是函數(shù)A2注冊(cè)的拂蝎,所以跳出panicA2的處理邏輯后穴墅,程序會(huì)恢復(fù)到函數(shù)A2的棧幀。


image.png

通過之前介紹的方式温自,最終會(huì)跳轉(zhuǎn)到A2這里玄货,調(diào)用deferreturn函數(shù)的地方繼續(xù)執(zhí)行,因?yàn)楹瘮?shù)A2注冊(cè)的defer函數(shù)B1已經(jīng)執(zhí)行完了悼泌,所以deferreturn函數(shù)返回松捉,然后函數(shù)A2返回。返回到哪里呢馆里,因?yàn)楹瘮?shù)A2是由panicA觸發(fā)調(diào)用的隘世,所以函數(shù)A2結(jié)束后,程序再次回到panicA的處理流程鸠踪,繼續(xù)執(zhí)行defer鏈表丙者。

簡(jiǎn)單回顧一下,B1中recover發(fā)生后营密,會(huì)跳出當(dāng)前執(zhí)行的panicA2處理流程械媒,恢復(fù)到注冊(cè)函數(shù)B1的函數(shù)棧幀。A2結(jié)束后會(huì)返回到觸發(fā)它執(zhí)行的panicA的處理流程评汰,現(xiàn)在你知道recover發(fā)生后纷捞,究竟會(huì)恢復(fù)到哪里了吧。

Go1.14版本以前键俱,panic和recover的基本處理流程就是這樣兰绣,由于Go1.14中使用了open coded defer,導(dǎo)致panic執(zhí)行defer鏈表時(shí)编振,不能如同之前這般輕松缀辩,需要通過掃描棧來(lái)找到未注冊(cè)到defer鏈表的defer函數(shù)偎快。但是panic和recover的總體設(shè)計(jì)思想鸵赖,在這些版本中都是一致的

總結(jié)
有defer有panic, defer中沒有recover且沒有panic
此種情況panic沒有嵌套,defer可以正常結(jié)束况木,唯一的一個(gè)panic執(zhí)行完defer鏈表畅蹂,就會(huì)打印panic信息健无。
有defer有panic, defer中有panic,但是沒有recover
出現(xiàn)panic嵌套液斜,defer無(wú)法正常結(jié)束累贤,此時(shí)叠穆,后面的panic會(huì)把前面的panic標(biāo)記為aborted
有defer有panic, defer中有recover,但是沒有panic
已標(biāo)記為recover的panic會(huì)被從panic鏈表中移除臼膏。不會(huì)在打印panic信息時(shí)輸出該panic信息
有defer有panic, defer中同時(shí)有recover, panic
此時(shí)硼被,recover中會(huì)被標(biāo)記,后面的panic會(huì)將該panic標(biāo)記為aborted渗磅。由于defer不能正常結(jié)束嚷硫,所以被標(biāo)記為recovered的panic仍然不能被移除。
————————————————

原文鏈接:https://blog.csdn.net/qq_42956653/article/details/121121451

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末始鱼,一起剝皮案震驚了整個(gè)濱河市仔掸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌医清,老刑警劉巖起暮,帶你破解...
    沈念sama閱讀 218,451評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異会烙,居然都是意外死亡鞋怀,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門持搜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人焙矛,你說我怎么就攤上這事葫盼。” “怎么了村斟?”我有些...
    開封第一講書人閱讀 164,782評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵贫导,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我蟆盹,道長(zhǎng)孩灯,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,709評(píng)論 1 294
  • 正文 為了忘掉前任逾滥,我火速辦了婚禮峰档,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘寨昙。我一直安慰自己讥巡,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,733評(píng)論 6 392
  • 文/花漫 我一把揭開白布舔哪。 她就那樣靜靜地躺著欢顷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪捉蚤。 梳的紋絲不亂的頭發(fā)上抬驴,一...
    開封第一講書人閱讀 51,578評(píng)論 1 305
  • 那天炼七,我揣著相機(jī)與錄音,去河邊找鬼布持。 笑死豌拙,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鳖链。 我是一名探鬼主播姆蘸,決...
    沈念sama閱讀 40,320評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼芙委!你這毒婦竟也來(lái)了逞敷?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,241評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤灌侣,失蹤者是張志新(化名)和其女友劉穎推捐,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體侧啼,經(jīng)...
    沈念sama閱讀 45,686評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡牛柒,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,878評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了痊乾。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片皮壁。...
    茶點(diǎn)故事閱讀 39,992評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖哪审,靈堂內(nèi)的尸體忽然破棺而出蛾魄,到底是詐尸還是另有隱情,我是刑警寧澤湿滓,帶...
    沈念sama閱讀 35,715評(píng)論 5 346
  • 正文 年R本政府宣布滴须,位于F島的核電站,受9級(jí)特大地震影響叽奥,放射性物質(zhì)發(fā)生泄漏扔水。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,336評(píng)論 3 330
  • 文/蒙蒙 一朝氓、第九天 我趴在偏房一處隱蔽的房頂上張望魔市。 院中可真熱鬧,春花似錦赵哲、人聲如沸嘹狞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)磅网。三九已至,卻和暖如春筷屡,著一層夾襖步出監(jiān)牢的瞬間涧偷,已是汗流浹背簸喂。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留燎潮,地道東北人喻鳄。 一個(gè)月前我還...
    沈念sama閱讀 48,173評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像确封,于是被迫代替她去往敵國(guó)和親除呵。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,947評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容