亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

一個(gè)線程罷工的詭異事件

BakerJ / 772人閱讀

摘要:結(jié)合之前的線程快照,我發(fā)現(xiàn)這個(gè)消費(fèi)線程也是處于狀態(tài),和后面的業(yè)務(wù)線程池一模一樣。本地模擬本地也是創(chuàng)建了一個(gè)單線程的線程池,分別執(zhí)行了兩個(gè)任務(wù)。發(fā)現(xiàn)當(dāng)任務(wù)中拋出一個(gè)沒有捕獲的異常時(shí),線程池中的線程就會(huì)處于狀態(tài),同時(shí)所有的堆棧都和生產(chǎn)相符。

背景

事情(事故)是這樣的,突然收到報(bào)警,線上某個(gè)應(yīng)用里業(yè)務(wù)邏輯沒有執(zhí)行,導(dǎo)致的結(jié)果是數(shù)據(jù)庫里的某些數(shù)據(jù)沒有更新。

雖然是前人寫的代碼,但作為 Bug maker&killer 只能咬著牙上了。

因?yàn)橹皼]有接觸過出問題這塊的邏輯,所以簡(jiǎn)單理了下如圖:

有一個(gè)生產(chǎn)線程一直源源不斷的往隊(duì)列寫數(shù)據(jù)。

消費(fèi)線程也一直不停的取出數(shù)據(jù)后寫入后續(xù)的業(yè)務(wù)線程池。

業(yè)務(wù)線程池里的線程會(huì)對(duì)每個(gè)任務(wù)進(jìn)行入庫操作。

整個(gè)過程還是比較清晰的,就是一個(gè)典型的生產(chǎn)者消費(fèi)者模型。

嘗試定位

接下來便是嘗試定位這個(gè)問題,首先例行檢查了以下幾項(xiàng):

是否內(nèi)存有內(nèi)存溢出?

應(yīng)用 GC 是否有異常?

通過日志以及監(jiān)控發(fā)現(xiàn)以上兩項(xiàng)都是正常的。

緊接著便 dump 了線程快照查看業(yè)務(wù)線程池中的線程都在干啥。

結(jié)果發(fā)現(xiàn)所有業(yè)務(wù)線程池都處于 waiting 狀態(tài),隊(duì)列也是空的。

同時(shí)生產(chǎn)者使用的隊(duì)列卻已經(jīng)滿了,沒有任何消費(fèi)跡象。

結(jié)合上面的流程圖不難發(fā)現(xiàn)應(yīng)該是消費(fèi)隊(duì)列的 Consumer 出問題了,導(dǎo)致上游的隊(duì)列不能消費(fèi),下有的業(yè)務(wù)線程池沒事可做。

review 代碼

于是查看了消費(fèi)代碼的業(yè)務(wù)邏輯,同時(shí)也發(fā)現(xiàn)消費(fèi)線程是一個(gè)單線程

結(jié)合之前的線程快照,我發(fā)現(xiàn)這個(gè)消費(fèi)線程也是處于 waiting 狀態(tài),和后面的業(yè)務(wù)線程池一模一樣。

他做的事情基本上就是對(duì)消息解析,之后丟到后面的業(yè)務(wù)線程池中,沒有發(fā)現(xiàn)什么特別的地方。

但是由于里面的分支特別多(switch case),看著有點(diǎn)頭疼;所以我與寫這個(gè)業(yè)務(wù)代碼的同學(xué)溝通后他告訴我確實(shí)也只是入口處解析了一下數(shù)據(jù),后續(xù)所有的業(yè)務(wù)邏輯都是丟到線程池中處理的,于是我便帶著這個(gè)前提去排查了(埋下了伏筆)。

因?yàn)檫@里消費(fèi)的隊(duì)列其實(shí)是一個(gè) disruptor 隊(duì)列;它和我們常用的 BlockQueue 不太一樣,不是由開發(fā)者自定義一個(gè)消費(fèi)邏輯進(jìn)行處理的;而是在初始化隊(duì)列時(shí)直接丟一個(gè)線程池進(jìn)去,它會(huì)在內(nèi)部使用這個(gè)線程池進(jìn)行消費(fèi),同時(shí)回調(diào)一個(gè)方法,在這個(gè)方法里我們寫自己的消費(fèi)邏輯。

所以對(duì)于開發(fā)者而言,這個(gè)消費(fèi)邏輯其實(shí)是一個(gè)黑盒。

于是在我反復(fù) review 了消費(fèi)代碼中的數(shù)據(jù)解析邏輯發(fā)現(xiàn)不太可能出現(xiàn)問題后,便開始瘋狂懷疑是不是 disruptor 自身的問題導(dǎo)致這個(gè)消費(fèi)線程罷工了。

再翻了一陣 disruptor 的源碼后依舊沒發(fā)現(xiàn)什么問題后我咨詢對(duì) disruptor 較熟的@咖啡拿鐵,在他的幫助下在本地模擬出來和生產(chǎn)一樣的情況。

本地模擬


本地也是創(chuàng)建了一個(gè)單線程的線程池,分別執(zhí)行了兩個(gè)任務(wù)。

第一個(gè)任務(wù)沒啥好說的,就是簡(jiǎn)單的打印。

第二個(gè)任務(wù)會(huì)對(duì)一個(gè)數(shù)進(jìn)行累加,加到 10 之后就拋出一個(gè)未捕獲的異常。

接著我們來運(yùn)行一下。


發(fā)現(xiàn)當(dāng)任務(wù)中拋出一個(gè)沒有捕獲的異常時(shí),線程池中的線程就會(huì)處于 waiting 狀態(tài),同時(shí)所有的堆棧都和生產(chǎn)相符。

細(xì)心的朋友會(huì)發(fā)現(xiàn)正常運(yùn)行的線程名稱和異常后處于 waiting 狀態(tài)的線程名稱是不一樣的,這個(gè)后續(xù)分析。
解決問題

當(dāng)加入異常捕獲后又如何呢?

程序肯定會(huì)正常運(yùn)行。

同時(shí)會(huì)發(fā)現(xiàn)所有的任務(wù)都是由一個(gè)線程完成的。

雖說就是加了一行代碼,但我們還是要搞清楚這里面的門門道道。

源碼分析

于是只有直接 debug 線程池的源碼最快了;

通過剛才的異常堆棧我們進(jìn)入到 ThreadPoolExecutor.java:1142 處。

發(fā)現(xiàn)線程池已經(jīng)幫我們做了異常捕獲,但依然會(huì)往上拋。

finally 塊中會(huì)執(zhí)行 processWorkerExit(w, completedAbruptly) 方法。

看過之前《如何優(yōu)雅的使用和理解線程池》的朋友應(yīng)該還會(huì)有印象。

線程池中的任務(wù)都會(huì)被包裝為一個(gè)內(nèi)部 Worker 對(duì)象執(zhí)行。

processWorkerExit 可以簡(jiǎn)單的理解為是把當(dāng)前運(yùn)行的線程銷毀(workers.remove(w))、同時(shí)新增(addWorker())一個(gè) Worker 對(duì)象接著處理;

就像是哪個(gè)零件壞掉后重新?lián)Q了一個(gè)新的接著工作,但是舊零件負(fù)責(zé)的任務(wù)就沒有了。

接下來看看 addWorker() 做了什么事情:

只看這次比較關(guān)心的部分;添加成功后會(huì)直接執(zhí)行他的 start() 的方法。

由于 Worker 實(shí)現(xiàn)了 Runnable 接口,所以本質(zhì)上就是調(diào)用了 runWorker() 方法。

runWorker() 其實(shí)就是上文 ThreadPoolExecutor 拋出異常時(shí)的那個(gè)方法。


它會(huì)從隊(duì)列里一直不停的獲取待執(zhí)行的任務(wù),也就是 getTask();在 getTask 也能看出它會(huì)一直從內(nèi)置的隊(duì)列取出任務(wù)。

而一旦隊(duì)列是空的,它就會(huì) waitingworkQueue.take(),也就是我們從堆棧中發(fā)現(xiàn)的 1067 行代碼。

線程名字的變化



上文還提到了異常后的線程名稱發(fā)生了改變,其實(shí)在 addWorker() 方法中可以看到 new Worker()時(shí)就會(huì)重新命名線程的名稱,默認(rèn)就是把后綴的計(jì)數(shù)+1。

這樣一切都能解釋得通了,真相只有一個(gè):

在單個(gè)線程的線程池中一但拋出了未被捕獲的異常時(shí),線程池會(huì)回收當(dāng)前的線程并創(chuàng)建一個(gè)新的 Worker
它也會(huì)一直不斷的從隊(duì)列里獲取任務(wù)來執(zhí)行,但由于這是一個(gè)消費(fèi)線程,根本沒有生產(chǎn)者往里邊丟任務(wù),所以它會(huì)一直 waiting 在從隊(duì)列里獲取任務(wù)處,所以也就造成了線上的隊(duì)列沒有消費(fèi),業(yè)務(wù)線程池沒有執(zhí)行的問題。
總結(jié)

所以之后線上的那個(gè)問題加上異常捕獲之后也變得正常了,但我還是有點(diǎn)納悶的是:

既然后續(xù)所有的任務(wù)都是在線程池中執(zhí)行的,也就是純異步了,那即便是出現(xiàn)異常也不會(huì)拋到消費(fèi)線程中啊。

這不是把我之前儲(chǔ)備的知識(shí)點(diǎn)推翻了嘛?不信邪!之后我讓運(yùn)維給了加上異常捕獲后的線上錯(cuò)誤日志。

結(jié)果發(fā)現(xiàn)在上文提到的眾多 switch case 中,最后一個(gè)竟然是直接操作的數(shù)據(jù)庫,導(dǎo)致一個(gè)非空字段報(bào)錯(cuò)了

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/73681.html

相關(guān)文章

  • 線程池中你不容錯(cuò)過一些細(xì)節(jié)

    摘要:第二還是大家對(duì)線程池的理解不夠深刻,比如今天要探討的內(nèi)容。我認(rèn)為線程池它就是一個(gè)調(diào)度任務(wù)的工具。而在線程池這個(gè)場(chǎng)景中卻恰好就是要利用它只是一個(gè)普通方法調(diào)用。 showImg(https://segmentfault.com/img/remote/1460000018653817); 背景 上周分享了一篇《一個(gè)線程罷工的詭異事件》,最近也在公司內(nèi)部分享了這個(gè)案例。 無獨(dú)有偶,在內(nèi)部分享的...

    kgbook 評(píng)論0 收藏0
  • 線程池沒你想那么簡(jiǎn)單(續(xù))

    摘要:前言前段時(shí)間寫過一篇線程池沒你想的那么簡(jiǎn)單,和大家一起擼了一個(gè)基本的線程池,具備線程池基本調(diào)度功能。線程池自動(dòng)擴(kuò)容縮容?;卣{(diào)以上就是線程池的構(gòu)造函數(shù)以及接口的定義。所以我們?cè)谑褂镁€程池時(shí),其中的任務(wù)一定要做好異常處理。線程異常捕獲的重要性。 showImg(https://segmentfault.com/img/remote/1460000019403163?w=1904&h=108...

    svtter 評(píng)論0 收藏0
  • 慎用ThreadLocal

    摘要:另載于是個(gè)很爽的東西,線程安全,能當(dāng)全局變量來用別。第一家公司,使用框架老技術(shù),現(xiàn)代人可以理解為類似,對(duì)每個(gè)請(qǐng)求都套上,進(jìn)入時(shí)把寫入,返回或拋?zhàn)⒁鈺r(shí)清理。第二家公司,某次引入一個(gè)設(shè)計(jì),也用了來傳遞上下文信息,有的地方?jīng)]能清掉。 另載于 http://www.qingjingjie.com/blogs/12 ThreadLocal是個(gè)很爽的東西,線程安全,能當(dāng)全局變量來用(別!)。 上一...

    harriszh 評(píng)論0 收藏0
  • 一個(gè) Reentrant Error 引發(fā)對(duì) Python 信號(hào)機(jī)制探索和思考

    摘要:倘若該回答是正確的,則立即有如下推論在處理信號(hào)的過程中,字節(jié)碼具有原子性。因此,除了在兩個(gè)字節(jié)碼之間,應(yīng)該還有其他時(shí)機(jī)喚起了。行的是信號(hào)處理函數(shù)的最外層包裝,由系統(tǒng)調(diào)用或注冊(cè)至內(nèi)核,并在信號(hào)發(fā)生時(shí)被內(nèi)核回調(diào),是異??刂屏鞯娜肟?。 寫在前面 前幾天工作時(shí)遇到了一個(gè)匪夷所思的問題。經(jīng)過幾次嘗試后問題得以解決,但問題產(chǎn)生的原因卻仍令人費(fèi)解。查找 SO 無果,我決定翻看 Python 的源碼。...

    shiguibiao 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

BakerJ

|高級(jí)講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<