同步(synchronous) IO和異步(asynchronous) IO,阻塞(blocking) IO和非阻塞(non-blocking)IO ,我相信這幾個(gè)詞困擾過(guò)很多人,更痛苦的是,如果你查閱過(guò)文獻(xiàn)資料,你會(huì)發(fā)現(xiàn)不同的資料中的解釋是不一樣的,例如在wiki中,異步和非阻塞被當(dāng)成了一個(gè)概念 。
出現(xiàn)這種情況的原因,我認(rèn)為很大程度上是因?yàn)镮O這個(gè)概念本身就很寬泛,它其實(shí)包含了好幾個(gè)層面。比如說(shuō),你可以把它看做是一個(gè)物理上的設(shè)備,也可以看做是 OS抽象出來(lái)的一個(gè)軟件,還可以看做是平時(shí)寫(xiě)程序用的read(),write()函數(shù),不同的層面對(duì)于這幾個(gè)詞的理解也是不一樣的。
先看一個(gè)較低的層次。如果從CPU的角度看,其實(shí)大部分的IO都是異步的:因?yàn)镃PU啟動(dòng)這個(gè)IO操作后,就去干其它的事情了,一直到產(chǎn)生一個(gè)中斷,告訴它IO完成了。
“Most physical I/O is asynchronous—the CPU starts the transfer and goes off to do something else until the interrupt arrives. User programs are much easier to write if the I/O operations are blocking—after a read system call the program is automatically suspended until the data are available in the buffer. It is up to the operating system to make operations that are actually interrupt-driven look blocking to the user programs.” (引自 Modern Operating Systems, 2ed)
不過(guò),本文并不想探究那么底層的東東。作為程序員,更多的還是從應(yīng)用層面來(lái)考慮。所以,以下重點(diǎn)介紹的是應(yīng)用程序中能夠采用的四種IO機(jī)制。
首先,從最常用到的,也是最容易理解的同步阻塞IO 說(shuō)起。
在這個(gè)模型中,應(yīng)用程序(application)為了執(zhí)行這個(gè)read操作,會(huì)調(diào)用相應(yīng)的一個(gè)system call,將系統(tǒng)控制權(quán)交給kernel,然后就進(jìn)行等待(這其實(shí)就是被阻塞了)。kernel開(kāi)始執(zhí)行這個(gè)system call,執(zhí)行完畢后會(huì)向應(yīng)用程序返回響應(yīng),應(yīng)用程序得到響應(yīng)后,就不再阻塞,并進(jìn)行后面的工作。
例如,“在調(diào)用 read 系統(tǒng)調(diào)用時(shí),應(yīng)用程序會(huì)阻塞并對(duì)內(nèi)核進(jìn)行上下文切換。然后會(huì)觸發(fā)讀操作,當(dāng)響應(yīng)返回時(shí)(從我們正在從中讀取的設(shè)備中返回),數(shù)據(jù)就被移動(dòng)到用戶(hù)空間的緩沖區(qū)中。然后應(yīng)用程序就會(huì)解除阻塞(read 調(diào)用返回)。”
舉一個(gè)淺顯的例子,就好比你去一個(gè)銀行柜臺(tái)存錢(qián)。首先,你會(huì)將存錢(qián)的單子填好,然后交給柜員。這里,你就好比是application,單子就是調(diào)用的 system call,柜員就是kernel。提交好單子后,你就坐在柜臺(tái)前等,相當(dāng)于開(kāi)始進(jìn)行等待。柜員辦好以后會(huì)給你一個(gè)回執(zhí),表示辦好了,這就是 response。然后你就可以拿著回執(zhí)干其它的事了。注意,這個(gè)時(shí)候,如果你辦完之后馬上去查賬,存的錢(qián)已經(jīng)打到你的賬戶(hù)上了。后面你會(huì)發(fā)現(xiàn),這點(diǎn)很重要。
接下來(lái)談同步非阻塞IO 。
在linux下,應(yīng)用程序可以通過(guò)設(shè)置文件描述符的屬性O(shè)_NONBLOCK,I/O操作可以立即返回,但是并不保證I/O操作成功。
也就是說(shuō),當(dāng)應(yīng)用程序設(shè)置了O_NONBLOCK之后,執(zhí)行write操作,調(diào)用相應(yīng)的system call,這個(gè)system call會(huì)從內(nèi)核中立即返回。但是在這個(gè)返回的時(shí)間點(diǎn),數(shù)據(jù)可能還沒(méi)有被真正的寫(xiě)入到指定的地方。也就是說(shuō),kernel只是很快的返回了這個(gè) system call(這樣,應(yīng)用程序不會(huì)被這個(gè)IO操作blocking),但是這個(gè)system call具體要執(zhí)行的事情(寫(xiě)數(shù)據(jù))可能并沒(méi)有完成。而對(duì)于應(yīng)用程序,雖然這個(gè)IO操作很快就返回了,但是它并不知道這個(gè)IO操作是否真的成功了,如果想知道,需要應(yīng)用程序主動(dòng)地去問(wèn)kernel。
這次不是去銀行存錢(qián),而是去銀行匯款。同樣的,你也需要填寫(xiě)匯款單然后交給柜員,柜員進(jìn)行一些簡(jiǎn)單的手續(xù)處理就能夠給你回執(zhí)。但是,你拿到回執(zhí)并不意味著錢(qián)已經(jīng)打到了對(duì)方的賬上。事實(shí)上,一般匯款的周期大概是24個(gè)小時(shí),如果你要以存錢(qián)的模式來(lái)匯款的話(huà),意味著你需要在銀行等24個(gè)小時(shí),這顯然是不現(xiàn)實(shí)的。所以,同步非阻塞IO在實(shí)際生活中也是有它的意義的。
再來(lái)談?wù)劗惒阶枞鸌O 。
在linux中,常常通過(guò)select/poll來(lái)實(shí)現(xiàn)這種機(jī)制。
和之前一樣,應(yīng)用程序要執(zhí)行read操作,因此調(diào)用一個(gè)system call,這個(gè)system call被傳遞給了kernel。但在應(yīng)用程序這邊,它調(diào)用system call之后,并不等待kernel返回response,這一點(diǎn)是和前面兩種機(jī)制不一樣的地方。這也是為什么它被稱(chēng)為異步的原因。但是為什么稱(chēng)其為阻塞呢?這是因?yàn)殡m然應(yīng)用程序是一個(gè)異步的方式,但是select()函數(shù)會(huì)將應(yīng)用程序阻塞住,一直等到這個(gè)system call有結(jié)果返回了,再通知應(yīng)用程序。也就是說(shuō),“在這種模型中,配置的是非阻塞 I/O,然后使用阻塞 select 系統(tǒng)調(diào)用來(lái)確定一個(gè) I/O 描述符何時(shí)有操作。”
所以,從IO操作的實(shí)際效果來(lái)看,異步阻塞IO和第一種同步阻塞IO是一樣的,應(yīng)用程序都是一直等到IO操作成功之后(數(shù)據(jù)已經(jīng)被寫(xiě)入或者讀?。砰_(kāi)始進(jìn)行下面的工作。異步阻塞IO的好處在于一個(gè)select函數(shù)可以為多個(gè)描述符提供通知,提高了并發(fā)性。
關(guān)于提高并發(fā)性這點(diǎn),我們還以銀行為例說(shuō)明。比如說(shuō)一個(gè)銀行柜臺(tái),現(xiàn)在有10個(gè)人想存錢(qián)。按照現(xiàn)在銀行的做法,一個(gè)個(gè)排隊(duì)。第一個(gè)人先填存款單,然后提交,然后柜員處理,然后給回執(zhí),成功后再輪到下一個(gè)人。大家應(yīng)該都在銀行排過(guò)對(duì),這樣的流程是很痛苦的。如果按照異步阻塞的機(jī)制,10個(gè)人都填好存款單,然后都提交給柜臺(tái),提交完之后所有的10個(gè)人就在銀行大廳等待。這時(shí)候會(huì)專(zhuān)門(mén)有個(gè)人,他會(huì)了解存款單處理的情況,一旦有存款單處理完畢,他會(huì)將回執(zhí)交給相應(yīng)的正在大廳等待的人,這個(gè)拿到回執(zhí)的人就可以去干其他的事情了。而前面提到的這個(gè)專(zhuān)人,就對(duì)應(yīng)于select函數(shù)。
最后,談?wù)劗惒椒亲枞鸌O 。
這個(gè)概念相對(duì)前面兩個(gè)反而更容易理解一些。
應(yīng)用程序提交read請(qǐng)求的system call,然后,kernel開(kāi)始處理相應(yīng)的IO操作,而同時(shí),應(yīng)用程序并不等kernel返回響應(yīng),就會(huì)開(kāi)始執(zhí)行其他的處理操作(應(yīng)用程序沒(méi)有被IO操作所阻塞)。當(dāng)kernel執(zhí)行完畢,返回read的響應(yīng),就會(huì)產(chǎn)生一個(gè)信號(hào)或執(zhí)行一個(gè)基于線(xiàn)程的回調(diào)函數(shù)來(lái)完成這次 I/O 處理過(guò)程。
比如銀行存錢(qián)?,F(xiàn)在某銀行新開(kāi)通了一項(xiàng)存錢(qián)業(yè)務(wù)。用戶(hù)之需要將存款單交給柜臺(tái),然后無(wú)需等待就可以離開(kāi)了。柜臺(tái)辦好以后會(huì)給用戶(hù)發(fā)送一條短信,告知交易成功。這樣用戶(hù)不需要在柜臺(tái)前進(jìn)行長(zhǎng)時(shí)間的等待,同時(shí),也能夠得到確切的消息知道交易完成。
從前面的介紹中可以看出,所謂的同步和異步,在這里指的是application和kernel之間的交互方式。如果application不需要等待 kernel的回應(yīng),那么它就是異步的。如果application提交完IO請(qǐng)求后,需要等待“回執(zhí)”,那么它就是同步的。
而阻塞和非阻塞,指的是application是否等待IO操作的完成。如果application必須等到IO操作實(shí)際完成以后再執(zhí)行下面的操作,那么它是阻塞的。反之,如果不等待IO操作的完成就開(kāi)始執(zhí)行其它操作,那么它是非阻塞的。
本文來(lái)自CSDN博客,轉(zhuǎn)載請(qǐng)標(biāo)明出處:http://blog.csdn.net/historyasamirror/archive/2009/06/15/4270633.aspx