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
我們已經(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è)颇蜡。
來(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鏈表了。
從頭開始執(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ā)的朽褪。
回到例子中置吓,A2執(zhí)行前也要先標(biāo)記,如果函數(shù)A2能正常結(jié)束缔赠,這一項(xiàng)defer就會(huì)被移除衍锚,繼續(xù)執(zhí)行下個(gè)defer。之所有這樣設(shè)計(jì)橡淑,是為了應(yīng)對(duì)defer函數(shù)沒有正常結(jié)束的情況构拳。
有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,
現(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)記為已終止尸折。
是時(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被終止。
這里不僅要把panicA標(biāo)識(shí)為已終止缴淋,還要把deferA1這一項(xiàng)移除
接下來(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
接下來(lái)增加recover看看, recover本身的邏輯很簡(jiǎn)單由捎,它只做一件事兔综,就是把當(dāng)前執(zhí)行的panic置為已恢復(fù),也就是把它的recovered字段置為true狞玛,其他的都不管软驰。
函數(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é)束著淆。
其實(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的處理流程。
但是要怎么跳出來(lái)呢?又恢復(fù)到哪里去呢荞膘?我們知道罚随,sp和pc是注冊(cè)defer函數(shù)時(shí)保存的,這里sp就是函數(shù)A的棧指針羽资,而pc字段淘菩,就是調(diào)用deferproc函數(shù)的返回地址(下一條指令的地址) ,這就是這段偽指令中判斷返回值是否大于零的這部分邏輯屠升。
通過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鏈表了 阿蝶。
不過要注意的是,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é)束习劫。
這就是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。
有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í)行的了。
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ì)被移除靴姿。
下面我們?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淤刃。
首先注冊(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鏈表。
首先是B1锌介,B1執(zhí)行時(shí)調(diào)用recover函數(shù)嗜诀,把panicA2置為已恢復(fù)猾警。
B1正常結(jié)束后,返回到panicA2的處理流程隆敢,檢測(cè)到panicA2已恢復(fù)发皿,把它從鏈表中移除 ,因?yàn)閐efer函數(shù)B1是函數(shù)A2注冊(cè)的拂蝎,所以跳出panicA2的處理邏輯后穴墅,程序會(huì)恢復(fù)到函數(shù)A2的棧幀。
通過之前介紹的方式温自,最終會(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