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