妙啊,阻塞到底是個(gè)啥?黃袍加身,亦能談古說今
本文轉(zhuǎn)載自微信公眾號「小姐姐味道」,作者小姐姐養(yǎng)的狗。轉(zhuǎn)載本文請聯(lián)系小姐姐味道公眾號。
現(xiàn)在,請記住你的身份!從進(jìn)入本篇文章開始,你就是皇帝!三宮六院七十二妃,任君品嘗。
人有親疏遠(yuǎn)近,事有輕重緩急。作為萬歲,你的時(shí)間非常寶貴。整個(gè)王朝都在你手中運(yùn)算,方能國泰民安。
為了討論方便,我們把場景界限在單核CPU上。你就是CPU,當(dāng)然是僅僅是一顆單核的CPU。
為了讓你更好的安排自己的時(shí)間,我將你的時(shí)間切割成了八九七十二份,每一份都彌足珍貴。
就憑我畫的這些密密麻麻的小方塊,你就應(yīng)該給xjjdog點(diǎn)下贊。
現(xiàn)實(shí)的CPU,時(shí)間片分的會(huì)更細(xì),但作為人類你是理解不了那么小的時(shí)間間隔的:你可能每天都要花很多時(shí)間在吃喝拉撒上,但后宮里總有大部分希望得到你寵幸的妃子,你一點(diǎn)時(shí)間片都不留給她。
實(shí)在是忙不過來呀!需要一個(gè)太監(jiān)!
1. 中斷就是從中斷掉
不是讓太監(jiān)來幫你干活的,他沒有那個(gè)能力。太監(jiān)是用來給你調(diào)度工作的。
比如,有反叛的軍隊(duì)攻到了城外,太監(jiān)慌慌張張來報(bào)告,你就不得不暫停后宮的活動(dòng),提著褲子處理首要的問題;再比如,有剛來的妃子頻頻拋媚眼,但你還有一大堆公文要批,心有余而力不足。
這種處理問題的方式,就是中斷(從中斷掉就是太監(jiān))。中斷是指在CPU正常運(yùn)行期間,由于內(nèi)外部事件或由程序預(yù)先安排的事件引起的 CPU 暫時(shí)停止正在運(yùn)行的程序,轉(zhuǎn)而為該內(nèi)部或外部事件或預(yù)先安排的事件服務(wù)的程序中去,服務(wù)完畢后再返回去繼續(xù)運(yùn)行被暫時(shí)中斷的程序。
我們來看下底層的中斷處理程序。
- request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
- const char *name, void *dev)
可以看到,太監(jiān)只需要給皇帝要做的事情,都編碼備案,并固定下處理流程,調(diào)整好優(yōu)先級,皇帝的時(shí)間片就可以有效的輪轉(zhuǎn)起來。不至于江山都丟了,還在后宮里風(fēng)花雪月。
拿網(wǎng)絡(luò)傳輸來說,當(dāng)有了網(wǎng)絡(luò)數(shù)據(jù)包,就需要及時(shí)處理,否則客戶端會(huì)超時(shí)。這個(gè)時(shí)候,網(wǎng)卡會(huì)立馬發(fā)出中斷請求,CPU就會(huì)通過網(wǎng)卡的中斷程序去處理這些緩沖區(qū)。這都是非常重要的工作。
中斷又有硬中斷和軟中斷之分。硬中斷是由硬件產(chǎn)生的,比如,像磁盤,網(wǎng)卡,鍵盤,時(shí)鐘等。軟中斷是由當(dāng)前正在運(yùn)行的進(jìn)程所產(chǎn)生的,通常優(yōu)先級比硬中斷低一些。
2. 阻塞會(huì)占用CPU么?
代入了皇帝這個(gè)身份,我們就可以解釋一些平常遇到的,令人疑惑的問題。
我們都見過在Concurent包下面,有一個(gè)叫做LinkedBlockingQeque的類。從它的名字就可以看出,這是一個(gè)阻塞隊(duì)列。實(shí)際上,它也并不是掛著羊頭賣狗肉。
如下面的代碼,我們通常把它放在循環(huán)中。我對while(true)這種東西是有心理陰影的,因?yàn)樗锌赡軙?huì)跑滿你的CPU。
- while(true){
- Object o = linkedBlockingQeque.poll();
- }
但實(shí)際上,并不會(huì)。因?yàn)槿思叶颊f了,這是個(gè)阻塞隊(duì)列。
相似的,還有NIO中的select。把邏輯放在while循環(huán)里,不怕得報(bào)應(yīng)么?
- while (!stop) {
- int num = selector.select();
- if (num == 0) {
- continue;
- }
- Iterator<SelectionKey> events = selector.selectedKeys().iterator();
- }
這還真不怕。因?yàn)樽枞⒉粫?huì)占用任何資源。
比如,小太監(jiān)上報(bào)了一個(gè)折子,是關(guān)于呂嬪妃的舅舅的貪污問題處理。但是這個(gè)問題,需要等待司法調(diào)查的結(jié)果,還需要聽聽愛妃的意見,就先可以把它擱置在一旁。
把問題記錄在一個(gè)其他的小冊子里,等這些依賴的事辦的差不多了,同時(shí)你又有龍時(shí),那就可以繼續(xù)處理。
可以看到,這種阻塞性的問題,雖然是個(gè)任務(wù),但并不會(huì)占用你的任何時(shí)間,這在計(jì)算機(jī)中是一樣的。
我們來看一下常見的Java阻塞方式。
sleep和wait
睡和等。用詞很巧妙,到底妙在哪呢?因?yàn)樗乾F(xiàn)實(shí)中的場景。
sleep
sleep函數(shù)會(huì)讓線程在一定的時(shí)間內(nèi)進(jìn)入阻塞狀態(tài),不能得到cpu時(shí)間,但不會(huì)釋放鎖資源。指定的時(shí)間一過,線程重新進(jìn)入可執(zhí)行狀態(tài)。
注意我們這里說的是線程,并不是CPU本身。線程不活動(dòng)了,并不代表CPU不能干其他事情。
比如,今天是接見大臣的黃道吉日,王天師得到了接見的機(jī)會(huì),其他的大臣們就得在外面等著被傳喚。結(jié)果王天師的談話又臭又長,勾不起你的任何興趣。正好小太監(jiān)急匆匆跑來,在你耳邊悄悄說:李貴妃生了個(gè)兒子!
這是讓人振奮的事情,因?yàn)槠渌麅鹤佣荚趯m斗中被KO了。于是你裝模作樣的對王天師說:我現(xiàn)在有點(diǎn)頭痛,需要小憩一會(huì)兒。” 其實(shí)你已經(jīng)偷偷去探望李貴妃了。
注意,這個(gè)時(shí)候,王天師只能唯唯諾諾的等著。對于“接見”這個(gè)主線來說,其他的大臣也只能在外面等著被傳喚。它們都沒有拿到“接見”這把鎖,王天師也一直占用著這把鎖,直到你看完了兒子歸來。
這就是sleep不釋放鎖的意思,因?yàn)閟leep后,在sleep那一瞬間的任何東西都沒有改變。
wait
wait( ) 使線程進(jìn)入阻塞狀態(tài),同時(shí)釋放自己占有的鎖資源,和notify( )搭配使用。
對于wait來說,就完全不一樣了。
如圖,每個(gè)監(jiān)視器(Monitor)在某個(gè)時(shí)刻,只能被一個(gè)線程擁有,該線程就是 “Active Thread”。而其它線程都是 “Waiting Thread”,分別在兩個(gè)隊(duì)列 “ Entry Set”和 “Wait Set”里面等候。在 “Entry Set”中等待的線程狀態(tài)是 “Waiting for monitor entry”,而在 “Wait Set”中等待的線程狀態(tài)是 “in Object.wait()”。
術(shù)語難以理解,還是以皇帝的身份來瀟灑一下。
這個(gè)時(shí)候,你還打算接見大臣。不過,現(xiàn)在不想再one by one了,因?yàn)檫@太低效太枯燥了。某個(gè)大臣在你的書房里待得長了些,就有可能有大臣懷疑你在搞gay,這種副作用讓人心里不悅。
p2p不行,那就聚在一塊談?wù)勑陌伞?/p>
正在和你談話的是王天師,因?yàn)檫@貨話比較多,你也比較喜歡他。
王天師說:小太子出生在三伏天,就叫史三伏吧!。
你這才想起自己姓史。作為熟讀文章的皇帝,你對此嗤之以鼻,聽著這不入流的名字,還隱隱有點(diǎn)生氣。
王愛卿,你還是先wait一下吧,聽聽別人意見。
這個(gè)時(shí)候,一大堆等著拍馬屁的大臣開始舉手,躍躍欲試。劉道長搶到了 談話主線 這把鎖。
劉道長: 天地長久,人有終時(shí),北冥有魚,其名為鯤,可活億年。我看,就叫史鯤吧。
你聽后微微頷首,果然仙人嘴下口水香,但總感覺有點(diǎn)怪異。
注意注意。等著發(fā)言的這群大臣,就叫做Entry Set,誰舉手舉得快,就可以回答這個(gè)問題。
像王天師這種被喊停的大臣,就屬于Wait Set,只有你重新讓他說話,他才有機(jī)會(huì)。
這整個(gè)過程,談話是可以繼續(xù)的,并不因?yàn)橥跆鞄煴唤粤苏勗捑蜔o法進(jìn)行下去。我們就可以說,wait操作是釋放了對象鎖的。
計(jì)算機(jī)中各種所謂的阻塞,都是通過劃分不同的隊(duì)列資源進(jìn)行處理。比如epoll就是圍繞著工作隊(duì)列和等待隊(duì)列進(jìn)行編程的。雖然底層的數(shù)據(jù)結(jié)構(gòu)有些不同,但思想都是一樣的。
線程如何獲取時(shí)間片?
這個(gè)不容易回答,因?yàn)槟阈枰酪粋€(gè)事實(shí):Java中的線程,在Linux上本質(zhì)是一個(gè)輕量級進(jìn)程,它的調(diào)度都是操作系統(tǒng)來完成的。
可以看一下我們最上面那一副讓人容易產(chǎn)生密集恐懼癥的圖片。我們的CPU時(shí)間,就劃分為多個(gè)CPU時(shí)間片。你的程序雖然在執(zhí)行while(true),但不代表它總能夠得到CPU資源,所以其他的進(jìn)程也有機(jī)會(huì)去執(zhí)行。
JVM采用搶占式調(diào)度模型,指的是讓優(yōu)先級高的線程占用比較多的CPU,如果線程優(yōu)先級相同,那么就隨機(jī)選擇一個(gè)線程,使其占用CPU。
注意“隨機(jī)”這兩個(gè)字,就非常的有魔性。它可以讓你每天都中100萬的彩票,也可能每天喝水都被嗆著。
可憐的計(jì)算機(jī)系統(tǒng),也參與到大千世界讓人無奈的隨機(jī)命運(yùn)而來。
但有一種很霸道的任務(wù),對CPU一搶一個(gè)準(zhǔn),那就是我們上面提到的硬中斷--那些不得不優(yōu)先處理的事情。
下輩子投胎,就當(dāng)個(gè)硬中斷吧(囧)。
快來點(diǎn)贊累加你的幸運(yùn)值吧 :)。
作者簡介:小姐姐味道 (xjjdog),一個(gè)不允許程序員走彎路的公眾號。聚焦基礎(chǔ)架構(gòu)和Linux。十年架構(gòu),日百億流量,與你探討高并發(fā)世界,給你不一樣的味道。我的個(gè)人微信xjjdog0,歡迎添加好友,進(jìn)一步交流。