日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

IO:同步,异步,阻塞,非阻塞

發(fā)布時(shí)間:2025/3/21 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 IO:同步,异步,阻塞,非阻塞 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

IO - 同步,異步,阻塞,非阻塞

都是老生常談的東西,多通讀幾遍,理解透徹!

????實(shí)際上同步與異步是針對(duì)應(yīng)用程序與內(nèi)核的交互而言的。同步過程中進(jìn)程觸發(fā)IO操作并等待(也就是我們說的阻塞)或者輪詢的去查看IO操作(也就是我們說的非阻塞)是否完成。?異步過程中進(jìn)程觸發(fā)IO操作以后,直接返回,做自己的事情,IO交給內(nèi)核來處理,完成后內(nèi)核通知進(jìn)程IO完成。

同步和異步針對(duì)應(yīng)用程序來,關(guān)注的是程序中間的協(xié)作關(guān)系;阻塞與非阻塞更關(guān)注的是單個(gè)進(jìn)程的執(zhí)行狀態(tài)。

同步有阻塞和非阻塞之分,異步?jīng)]有,它一定是非阻塞的。

阻塞、非阻塞、多路IO復(fù)用,都是同步IO,異步必定是非阻塞的,所以不存在異步阻塞和異步非阻塞的說法。

真正的異步IO需要CPU的深度參與。換句話說,只有用戶線程在操作IO的時(shí)候根本不去考慮IO的執(zhí)行全部都交給CPU去完成,而自己只等待一個(gè)完成信號(hào)的時(shí)候,才是真正的異步IO。

所以,拉一個(gè)子線程去輪詢、去死循環(huán),或者使用select、poll、epool,都不是異步。

同步:執(zhí)行一個(gè)操作之后,進(jìn)程觸發(fā)IO操作并等待(也就是我們說的阻塞)或者輪詢的去查看IO操作(也就是我們說的非阻塞)是否完成,等待結(jié)果,然后才繼續(xù)執(zhí)行后續(xù)的操作。

異步:執(zhí)行一個(gè)操作后,可以去執(zhí)行其他的操作,然后等待通知再回來執(zhí)行剛才沒執(zhí)行完的操作。

阻塞:進(jìn)程給CPU傳達(dá)一個(gè)任務(wù)之后,一直等待CPU處理完成,然后才執(zhí)行后面的操作。

非阻塞:進(jìn)程給CPU傳達(dá)任我后,繼續(xù)處理后續(xù)的操作,隔斷時(shí)間再來詢問之前的操作是否完成。這樣的過程其實(shí)也叫輪詢。



同步(synchronous) IO和異步(asynchronous) IO,阻塞(blocking) IO和非阻塞(non-blocking)IO分別是什么,到底有什么區(qū)別?這個(gè)問題其實(shí)不同的人給出的答案都可能不同,比如wiki,就認(rèn)為asynchronous IO和non-blocking IO是一個(gè)東西。這其實(shí)是因?yàn)椴煌娜说闹R(shí)背景不同,并且在討論這個(gè)問題的時(shí)候上下文(context)也不相同。

1.???Stevens在文章中一共比較了五種IO Model:
???blocking IO
????non-blocking IO
????IO multiplexing

????signal driven IO
???asynchronous IO

由于signaldriven IO在實(shí)際中并不常用,所以我這只提及剩下的四種IO Model。

再說一下IO發(fā)生時(shí)涉及的對(duì)象和步驟。
對(duì)于一個(gè)network IO (這里我們以read舉例),它會(huì)涉及到兩個(gè)系統(tǒng)對(duì)象,一個(gè)是調(diào)用這個(gè)IO的process (or thread),另一個(gè)就是系統(tǒng)內(nèi)核(kernel)。當(dāng)一個(gè)read操作發(fā)生時(shí),它會(huì)經(jīng)歷兩個(gè)階段:

1. 等待數(shù)據(jù)準(zhǔn)備 (Waiting for the data to be ready)。對(duì)于一個(gè)套接口上的操作,這一步驟關(guān)系到數(shù)據(jù)從網(wǎng)絡(luò)到達(dá),并將其復(fù)制到內(nèi)核的某個(gè)緩沖區(qū)。

2.????將數(shù)據(jù)從內(nèi)核緩沖區(qū)復(fù)制到進(jìn)程緩沖區(qū)(copying the data from the kernel to theprocess)。
記住這兩點(diǎn)很重要,因?yàn)檫@些IO Model的區(qū)別就是在兩個(gè)階段上各有不同的情況。

?

blocking IO?
在linux中,默認(rèn)情況下所有的socket都是blocking,一個(gè)典型的讀操作流程大概是這樣:


當(dāng)用戶進(jìn)程調(diào)用了recvfrom這個(gè)系統(tǒng),kernel就開始了IO的第一個(gè)階段:準(zhǔn)備數(shù)據(jù)。對(duì)于network io來說,很多時(shí)候數(shù)據(jù)在一開始還沒有到達(dá)(比如,還沒有收到一個(gè)完整的UDP包),這個(gè)時(shí)候kernel就要等待足夠的數(shù)據(jù)到來。而在用戶進(jìn)程這邊,整個(gè)進(jìn)程會(huì)被阻塞。當(dāng)kernel一直等到數(shù)據(jù)準(zhǔn)備好了,它就會(huì)將數(shù)據(jù)從kernel中拷貝到用戶內(nèi)存,然后kernel返回結(jié)果,用戶進(jìn)程才解除block的狀態(tài),重新運(yùn)行起來。
所以,blocking IO的特點(diǎn)就是在IO執(zhí)行的兩個(gè)階段都被block了。

?

non-blocking IO

linux下,可以通過設(shè)置socket使其變?yōu)閚on-blocking。當(dāng)對(duì)一個(gè)non-blocking socket執(zhí)行讀操作時(shí),流程是這個(gè)樣子:


從圖中可以看出,當(dāng)用戶進(jìn)程發(fā)出read操作時(shí),如果kernel中的數(shù)據(jù)還沒有準(zhǔn)備好,那么它并不會(huì)block用戶進(jìn)程,而是立刻返回一個(gè)error。從用戶進(jìn)程角度講,它發(fā)起一個(gè)read操作后,并不需要等待,而是馬上就得到了一個(gè)結(jié)果。用戶進(jìn)程判斷結(jié)果是一個(gè)error時(shí),它就知道數(shù)據(jù)還沒有準(zhǔn)備好,于是它可以再次發(fā)送read操作。一旦kernel中的數(shù)據(jù)準(zhǔn)備好了,并且又再次收到了用戶進(jìn)程的system call,那么它馬上就將數(shù)據(jù)拷貝到了用戶內(nèi)存,然后返回。
所以,用戶進(jìn)程其實(shí)是需要不斷的主動(dòng)詢問kernel數(shù)據(jù)好了沒有。

?

IO multiplexing

正因?yàn)樽枞鸌/O只能阻塞一個(gè)I/O操作,而I/O復(fù)用模型能夠阻塞多個(gè)I/O操作,所以才叫做多路復(fù)用。


?此模型用到select和poll函數(shù),這兩個(gè)函數(shù)也會(huì)使進(jìn)程阻塞,select先阻塞,有活動(dòng)套接字才返回,但是和阻塞I/O不同的是,這兩個(gè)函數(shù)可以同時(shí)阻塞多個(gè)I/O操作,而且可以同時(shí)對(duì)多個(gè)讀操作,多個(gè)寫操作的I/O函數(shù)進(jìn)行檢測(cè),直到有數(shù)據(jù)可讀或可寫(就是監(jiān)聽多個(gè)socket)。select被調(diào)用后,進(jìn)程會(huì)被阻塞,內(nèi)核監(jiān)視所有select負(fù)責(zé)的socket,當(dāng)有任何一個(gè)socket的數(shù)據(jù)準(zhǔn)備好了,select就會(huì)返回套接字可讀,我們就可以調(diào)用recvfrom處理數(shù)據(jù)。


?

Asynchronous I/O

linux下的asynchronous IO其實(shí)用得很少。先看一下它的流程:


用戶進(jìn)程發(fā)起read操作之后,立刻就可以開始去做其它的事。而另一方面,從kernel的角度,當(dāng)它受到一個(gè)asynchronous read之后,首先它會(huì)立刻返回,所以不會(huì)對(duì)用戶進(jìn)程產(chǎn)生任何block。然后,kernel會(huì)等待數(shù)據(jù)準(zhǔn)備完成,然后將數(shù)據(jù)拷貝到用戶內(nèi)存,當(dāng)這一切都完成之后,kernel會(huì)給用戶進(jìn)程發(fā)送一個(gè)signal,告訴它read操作完成了。

?

?

到目前為止,已經(jīng)將四個(gè)IO Model都介紹完了。現(xiàn)在回過頭來回答最初的那幾個(gè)問題:

blocking和non-blocking的區(qū)別在哪,

synchronous IO和asynchronous IO的區(qū)別在哪。
先回答最簡(jiǎn)單的這個(gè):

blocking vs non-blocking:前面的介紹中其實(shí)已經(jīng)很明確的說明了這兩者的區(qū)別。調(diào)用blockingIO會(huì)一直block住對(duì)應(yīng)的進(jìn)程直到操作完成,而non-blockingIO在kernel還沒準(zhǔn)備好數(shù)據(jù)的情況下會(huì)立刻返回。

在說明synchronous IO和asynchronous IO的區(qū)別之前,需要先給出兩者的定義。Stevens給出的定義(其實(shí)是POSIX的定義)是這樣子的:
????A synchronous I/O operation causes the requestingprocess to be blocked until that?I/O operation?completes;
????An asynchronous I/O operation does not cause the requestingprocess to be blocked;?
兩者的區(qū)別就在于synchronous IO(同步IO)做”IOoperation”的時(shí)候會(huì)將process阻塞。按照這個(gè)定義,之前所述的blocking IO,non-blocking IO,IO multiplexing都屬于synchronous IO。有人可能會(huì)說,non-blocking IO并沒有被block啊。這里有個(gè)非常“狡猾”的地方,定義中所指的”IO operation”是指真實(shí)的IO操作,就是例子中的recvfrom這個(gè)system call。non-blocking IO在執(zhí)行recvfrom這個(gè)system call的時(shí)候,如果kernel的數(shù)據(jù)沒有準(zhǔn)備好,這時(shí)候不會(huì)block進(jìn)程。但是,當(dāng)kernel中數(shù)據(jù)準(zhǔn)備好的時(shí)候,recvfrom會(huì)將數(shù)據(jù)從kernel拷貝到用戶內(nèi)存中,這個(gè)時(shí)候進(jìn)程是被block了,在這段時(shí)間內(nèi),進(jìn)程是被block的(未傳好的IO全部都是同步IO)。而asynchronousIO則不一樣,當(dāng)進(jìn)程發(fā)起IO 操作之后,就直接返回再也不理睬了,直到kernel發(fā)送一個(gè)信號(hào),告訴進(jìn)程說IO完成。在這整個(gè)過程中,進(jìn)程完全沒有被block。

各個(gè)IO Model的比較如圖所示:


經(jīng)過上面的介紹,會(huì)發(fā)現(xiàn)non-blocking IO和asynchronous IO的區(qū)別還是很明顯的。在non-blocking IO中,雖然進(jìn)程大部分時(shí)間都不會(huì)被block,但是它仍然要求進(jìn)程去主動(dòng)的check,并且當(dāng)數(shù)據(jù)準(zhǔn)備完成以后,也需要進(jìn)程主動(dòng)的再次調(diào)用recvfrom來將數(shù)據(jù)拷貝到用戶內(nèi)存(此時(shí)就是同步了)。

而asynchronous IO則完全不同。它就像是用戶進(jìn)程將整個(gè)IO操作交給了他人(kernel)完成,然后他人做完后發(fā)信號(hào)通知。在此期間,用戶進(jìn)程不需要去檢查IO操作的狀態(tài),也不需要主動(dòng)的去拷貝數(shù)據(jù)(此時(shí)就是異步了)。



高性能IO模型淺析?

?

服務(wù)器端編程經(jīng)常需要構(gòu)造高性能的IO模型,常見的IO模型有四種:

(1)同步阻塞IO(Blocking?IO):即傳統(tǒng)的IO模型。

(2)同步非阻塞IO(Non-blocking?IO):默認(rèn)創(chuàng)建的socket都是阻塞的,非阻塞IO要求socket被設(shè)置為NONBLOCK。注意這里所說的NIO并非Java的NIO(New?IO)庫。

(3)IO多路復(fù)用(IO?Multiplexing):即經(jīng)典的Reactor設(shè)計(jì)模式,Java中的Selector和Linux中的epoll都是這種模型。

(4)異步IO(Asynchronous?IO):即經(jīng)典的Proactor設(shè)計(jì)模式,也稱為異步非阻塞IO。?

為了方便描述,我們統(tǒng)一使用IO的讀操作作為示例。

?

一、同步阻塞IO

?

同步阻塞IO模型是最簡(jiǎn)單的IO模型,用戶線程在內(nèi)核進(jìn)行IO操作時(shí)被阻塞。


圖1?同步阻塞IO

如圖1所示,用戶線程通過系統(tǒng)調(diào)用read發(fā)起IO讀操作,由用戶空間轉(zhuǎn)到內(nèi)核空間。內(nèi)核等到數(shù)據(jù)包到達(dá)后,然后將接收的數(shù)據(jù)拷貝到用戶空間,完成read操作。

用戶線程使用同步阻塞IO模型的偽代碼描述為:


{
?
read(socket, buffer);
?
process(buffer);
?
}

?

即用戶需要等待read將socket中的數(shù)據(jù)讀取到buffer后,才繼續(xù)處理接收的數(shù)據(jù)。整個(gè)IO請(qǐng)求的過程中,用戶線程是被阻塞的,這導(dǎo)致用戶在發(fā)起IO請(qǐng)求時(shí),不能做任何事情,對(duì)CPU的資源利用率不夠。

?

二、同步非阻塞IO

?

同步非阻塞IO是在同步阻塞IO的基礎(chǔ)上,將socket設(shè)置為NONBLOCK。這樣做用戶線程可以在發(fā)起IO請(qǐng)求后可以立即返回。


圖2?同步非阻塞IO

如圖2所示,由于socket是非阻塞的方式,因此用戶線程發(fā)起IO請(qǐng)求時(shí)立即返回。但并未讀取到任何數(shù)據(jù),用戶線程需要不斷地發(fā)起IO請(qǐng)求,直到數(shù)據(jù)到達(dá)后,才真正讀取到數(shù)據(jù),繼續(xù)執(zhí)行。

用戶線程使用同步非阻塞IO模型的偽代碼描述為:


{
?
while(read(socket, buffer) != SUCCESS)
?
;
?
process(buffer);
?
}

?

即用戶需要不斷地調(diào)用read,嘗試讀取socket中的數(shù)據(jù),直到讀取成功后,才繼續(xù)處理接收的數(shù)據(jù)。整個(gè)IO請(qǐng)求的過程中,雖然用戶線程每次發(fā)起IO請(qǐng)求后可以立即返回,但是為了等到數(shù)據(jù),仍需要不斷地輪詢、重復(fù)請(qǐng)求,消耗了大量的CPU的資源。一般很少直接使用這種模型,而是在其他IO模型中使用非阻塞IO這一特性。

?

三、IO多路復(fù)用

IO多路復(fù)用模型是建立在內(nèi)核提供的多路分離函數(shù)select基礎(chǔ)之上的,使用select函數(shù)可以避免同步非阻塞IO模型中輪詢等待的問題。


如圖3所示,用戶首先將需要進(jìn)行IO操作的socket添加到select中,然后阻塞等待select系統(tǒng)調(diào)用返回。當(dāng)數(shù)據(jù)到達(dá)時(shí),socket被激活,select函數(shù)返回。用戶線程正式發(fā)起read請(qǐng)求,讀取數(shù)據(jù)并繼續(xù)執(zhí)行。

從流程上來看,使用select函數(shù)進(jìn)行IO請(qǐng)求和同步阻塞模型沒有太大的區(qū)別,甚至還多了添加監(jiān)視socket,以及調(diào)用select函數(shù)的額外操作,效率更差。但是,使用select以后最大的優(yōu)勢(shì)是用戶可以在一個(gè)線程內(nèi)同時(shí)處理多個(gè)socket的IO請(qǐng)求。用戶可以注冊(cè)多個(gè)socket,然后不斷地調(diào)用select讀取被激活的socket,即可達(dá)到在同一個(gè)線程內(nèi)同時(shí)處理多個(gè)IO請(qǐng)求的目的。而在同步阻塞模型中,必須通過多線程的方式才能達(dá)到這個(gè)目的。

用戶線程使用select函數(shù)的偽代碼描述為:


{
?
select(socket);
?
while(1) {
?
sockets = select();
?
for(socket in sockets) {
?
if(can_read(socket)) {
?
read(socket, buffer);
?
process(buffer);
?
}
?
}
?
}
?
}

?

其中while循環(huán)前將socket添加到select監(jiān)視中,然后在while內(nèi)一直調(diào)用select獲取被激活的socket,一旦socket可讀,便調(diào)用read函數(shù)將socket中的數(shù)據(jù)讀取出來。

?????然而,使用select函數(shù)的優(yōu)點(diǎn)并不僅限于此。雖然上述方式允許單線程內(nèi)處理多個(gè)IO請(qǐng)求,但是每個(gè)IO請(qǐng)求的過程還是阻塞的(在select函數(shù)上阻塞),平均時(shí)間甚至比同步阻塞IO模型還要長(zhǎng)。

如果用戶線程只注冊(cè)自己感興趣的socket或者IO請(qǐng)求,然后去做自己的事情,等到數(shù)據(jù)到來時(shí)再進(jìn)行處理,則可以提高CPU的利用率。

IO多路復(fù)用模型使用了Reactor(加入個(gè)事件分發(fā)器)設(shè)計(jì)模式實(shí)現(xiàn)了這一機(jī)制。


圖4?Reactor設(shè)計(jì)模式

如圖4所示,

EventHandler抽象類(接口)表示IO事件處理器,它擁有IO文件句柄Handle(通過get_handle獲取),以及對(duì)Handle的操作handle_event(讀/寫等)。

繼承于EventHandler的子類可以對(duì)事件處理器的行為進(jìn)行定制。

Reactor類用于管理EventHandler(注冊(cè)、刪除等),并使用handle_events實(shí)現(xiàn)事件循環(huán),不斷調(diào)用同步事件多路分離器(一般是內(nèi)核)的多路分離函數(shù)select,只要某個(gè)文件句柄被激活(可讀/寫等),select就返回(阻塞),handle_events就會(huì)調(diào)用與文件句柄關(guān)聯(lián)的事件處理器的handle_event進(jìn)行相關(guān)操作。

Synchronous Event Demultiplexer:同步事件分離器,阻塞等待Handles中的事件發(fā)生


圖5?IO多路復(fù)用

如圖5所示,通過Reactor的方式,可以將用戶線程輪詢IO操作狀態(tài)的工作統(tǒng)一交給handle_events事件循環(huán)進(jìn)行處理。用戶線程注冊(cè)事件處理器之后可以繼續(xù)執(zhí)行做其他的工作(異步),而Reactor線程負(fù)責(zé)調(diào)用內(nèi)核(事件分離器)的select函數(shù)檢查socket狀態(tài)。當(dāng)有socket被激活時(shí),則通知相應(yīng)的用戶線程(或執(zhí)行用戶線程的回調(diào)函數(shù)),執(zhí)行handle_event進(jìn)行數(shù)據(jù)讀取、處理的工作。

由于select函數(shù)是阻塞的,因此多路IO復(fù)用模型也被稱為異步阻塞IO模型。注意,這里的所說的阻塞是指select函數(shù)執(zhí)行時(shí)線程被阻塞,而不是指socket。

一般在使用IO多路復(fù)用模型時(shí),socket都是設(shè)置為NON-BLOCK的,不過這并不會(huì)產(chǎn)生影響,因?yàn)橛脩舭l(fā)起IO請(qǐng)求時(shí),數(shù)據(jù)已經(jīng)到達(dá)了,用戶線程一定不會(huì)被阻塞。

用戶線程使用IO多路復(fù)用模型的偽代碼描述為:


void UserEventHandler::handle_event() {
?
if(can_read(socket)) {
?
read(socket, buffer);
?
process(buffer);
?
}
?
}
?
?
?
{
?
Reactor.register(new UserEventHandler(socket));
?
}

?

用戶需要重寫EventHandler的handle_event函數(shù)進(jìn)行讀取數(shù)據(jù)、處理數(shù)據(jù)的工作,用戶線程只需要將自己的EventHandler注冊(cè)到Reactor即可。Reactor中handle_events事件循環(huán)的偽代碼大致如下。


Reactor::handle_events() {
?
while(1) {
?
sockets = select();
?
for(socket in sockets) {
?
get_event_handler(socket).handle_event();
?
}
?
}
?
}

?

事件循環(huán)不斷地調(diào)用select獲取被激活的socket,然后根據(jù)獲取socket對(duì)應(yīng)的EventHandler,執(zhí)行器handle_event函數(shù)即可。

IO多路復(fù)用是最常使用的IO模型,但是其異步程度還不夠“徹底”,因?yàn)樗褂昧藭?huì)阻塞線程的select系統(tǒng)調(diào)用。因此IO多路復(fù)用只能稱為異步阻塞IO,而非真正的異步IO。

?

四、異步IO

?

“真正”的異步IO需要操作系統(tǒng)更強(qiáng)的支持。

在IO多路復(fù)用模型中,事件循環(huán)將文件句柄的狀態(tài)事件通知給用戶線程,由用戶線程自行讀取數(shù)據(jù)、處理數(shù)據(jù)。

而在異步IO模型中,當(dāng)用戶線程收到通知時(shí),數(shù)據(jù)已經(jīng)被內(nèi)核讀取完畢,并放在了用戶線程指定的緩沖區(qū)內(nèi),內(nèi)核在IO完成后通知用戶線程直接使用即可。

?

異步IO模型使用了Proactor設(shè)計(jì)模式實(shí)現(xiàn)了這一機(jī)制。


圖6?Proactor設(shè)計(jì)模式

如圖6,Proactor模式和Reactor模式在結(jié)構(gòu)上比較相似,不過在用戶(Client)使用方式上差別較大。

Reactor模式中,用戶線程通過向Reactor對(duì)象注冊(cè)感興趣的事件監(jiān)聽,然后事件觸發(fā)時(shí)調(diào)用事件處理函數(shù)。

而Proactor模式中,用戶線程將AsynchronousOperation(讀/寫等)、Proactor以及操作完成時(shí)的CompletionHandler注冊(cè)到AsynchronousOperationProcessor。AsynchronousOperationProcessor使用Facade模式提供了一組異步操作API(讀/寫等)供用戶使用,當(dāng)用戶線程調(diào)用異步API后,便繼續(xù)執(zhí)行自己的任務(wù)。AsynchronousOperationProcessor?會(huì)開啟獨(dú)立的內(nèi)核線程執(zhí)行異步操作,實(shí)現(xiàn)真正的異步。當(dāng)異步IO操作完成時(shí),AsynchronousOperationProcessor將用戶線程與AsynchronousOperation一起注冊(cè)的Proactor和CompletionHandler取出,然后將CompletionHandler與IO操作的結(jié)果數(shù)據(jù)一起轉(zhuǎn)發(fā)給Proactor,Proactor負(fù)責(zé)回調(diào)每一個(gè)異步操作的事件完成處理函數(shù)handle_event。雖然Proactor模式中每個(gè)異步操作都可以綁定一個(gè)Proactor對(duì)象,但是一般在操作系統(tǒng)中,Proactor被實(shí)現(xiàn)為Singleton模式,以便于集中化分發(fā)操作完成事件。


圖7?異步IO

如圖7所示,異步IO模型中,用戶線程直接使用內(nèi)核提供的異步IO?API發(fā)起read請(qǐng)求,且發(fā)起后立即返回,繼續(xù)執(zhí)行用戶線程代碼。不過此時(shí)用戶線程已經(jīng)將調(diào)用的AsynchronousOperation和CompletionHandler注冊(cè)到內(nèi)核,然后操作系統(tǒng)開啟獨(dú)立的內(nèi)核線程去處理IO操作。

當(dāng)read請(qǐng)求的數(shù)據(jù)到達(dá)時(shí),由內(nèi)核負(fù)責(zé)讀取socket中的數(shù)據(jù),并寫入用戶指定的緩沖區(qū)中。最后內(nèi)核將read的數(shù)據(jù)和用戶線程注冊(cè)的CompletionHandler分發(fā)給內(nèi)部Proactor,Proactor將IO完成的信息通知給用戶線程(一般通過調(diào)用用戶線程注冊(cè)的完成事件處理函數(shù)),完成異步IO。

用戶線程使用異步IO模型的偽代碼描述為:


void UserCompletionHandler::handle_event(buffer) {
?
process(buffer);
?
}
?
?
?
{
?
aio_read(socket, new UserCompletionHandler);
?
}

?

用戶需要重寫CompletionHandler的handle_event函數(shù)進(jìn)行處理數(shù)據(jù)的工作,參數(shù)buffer表示Proactor已經(jīng)準(zhǔn)備好的數(shù)據(jù),用戶線程直接調(diào)用內(nèi)核提供的異步IO?API,并將重寫的CompletionHandler注冊(cè)即可。

相比于IO多路復(fù)用模型,異步IO并不十分常用,不少高性能并發(fā)服務(wù)程序使用IO多路復(fù)用模型+多線程任務(wù)處理的架構(gòu)基本可以滿足需求。況且目前操作系統(tǒng)對(duì)異步IO的支持并非特別完善,更多的是采用IO多路復(fù)用模型模擬異步IO的方式(IO事件觸發(fā)時(shí)不直接通知用戶線程,而是將數(shù)據(jù)讀寫完畢后放到用戶指定的緩沖區(qū)中)。Java7之后已經(jīng)支持了異步IO,感興趣的讀者可以嘗試使用。

?

?

一般來說,服務(wù)器端的I/O主要有兩種情況:一是來自網(wǎng)絡(luò)的I/O;二是對(duì)文件(設(shè)備)的I/O。
首先一個(gè)IO操作其實(shí)分成了兩個(gè)步驟:發(fā)起IO請(qǐng)求和實(shí)際的IO操作,

阻塞IO和非阻塞IO的區(qū)別在于第一步,

發(fā)起IO請(qǐng)求線程是否會(huì)被阻塞,如果阻塞直到完成那么就是傳統(tǒng)的阻塞IO,

如果不阻塞,那么就是非阻塞IO。
同步IO和異步IO的區(qū)別就在于第二個(gè)步驟是否阻塞,

如果實(shí)際的IO讀寫阻塞請(qǐng)求進(jìn)程,那么就是同步IO,因此阻塞IO、非阻塞IO、IO復(fù)用、信號(hào)驅(qū)動(dòng)IO都是同步IO,

如果不阻塞,而是操作系統(tǒng)幫你做完IO操作再將結(jié)果返回給你,那么就是異步IO。


當(dāng)一個(gè)read操作發(fā)生時(shí),會(huì)經(jīng)過如下流程:
通過read系統(tǒng)調(diào)用向內(nèi)核發(fā)起讀請(qǐng)求。
內(nèi)核向硬件發(fā)送讀指令,并等待讀就緒。
內(nèi)核把將要讀取的數(shù)據(jù)復(fù)制到描述符所指向的內(nèi)核緩存區(qū)中。
將數(shù)據(jù)從內(nèi)核緩存區(qū)拷貝到用戶進(jìn)程空間中。


同步IO和異步IO是針對(duì)應(yīng)用程序和內(nèi)核的交互而言的。
同步IO指的是用戶進(jìn)程觸發(fā)I/O操作并等待或者輪詢的去查看I/O操作是否就緒。
異步IO是指用戶進(jìn)程觸發(fā)I/O操作以后就立即返回,繼續(xù)開始做自己的事情,而當(dāng)I/O操作已經(jīng)完成的時(shí)候會(huì)得到I/O完成的通知。

同步IO在讀取數(shù)據(jù)時(shí),都要在同步流程中進(jìn)行一次內(nèi)核態(tài)到用戶態(tài)的數(shù)據(jù)拷貝。
異步IO需要操作系統(tǒng)更強(qiáng)的支持,操作系統(tǒng)開啟獨(dú)立的內(nèi)核線程去處理IO操作。當(dāng)read請(qǐng)求的數(shù)據(jù)到達(dá)時(shí),由內(nèi)核負(fù)責(zé)讀取socket中的數(shù)據(jù),并寫入用戶指定的緩沖區(qū)中,最后內(nèi)核通知用戶線程。異步IO接受到內(nèi)核通知時(shí),已經(jīng)完成了內(nèi)核態(tài)到用戶態(tài)的數(shù)據(jù)拷貝。

?

2.1、阻塞I/O

應(yīng)用程序調(diào)用一個(gè)IO函數(shù),導(dǎo)致應(yīng)用程序阻塞,等待數(shù)據(jù)準(zhǔn)備好。如果數(shù)據(jù)沒有準(zhǔn)備好,一直等待….數(shù)據(jù)準(zhǔn)備好了,從內(nèi)核拷貝到用戶空間,IO函數(shù)返回成功指示。

阻塞I/O模型圖:在調(diào)用recv()/recvfrom()函數(shù)時(shí),發(fā)生在內(nèi)核中等待數(shù)據(jù)和復(fù)制數(shù)據(jù)的過程。

當(dāng)調(diào)用recv()函數(shù)時(shí),系統(tǒng)首先查是否有準(zhǔn)備好的數(shù)據(jù)。如果數(shù)據(jù)沒有準(zhǔn)備好,那么系統(tǒng)就處于等待狀態(tài)。當(dāng)數(shù)據(jù)準(zhǔn)備好后,將數(shù)據(jù)從系統(tǒng)緩沖區(qū)復(fù)制到用戶空間,然后該函數(shù)返回。在套接應(yīng)用程序中,當(dāng)調(diào)用recv()函數(shù)時(shí),未必用戶空間就已經(jīng)存在數(shù)據(jù),那么此時(shí)recv()函數(shù)就會(huì)處于等待狀態(tài)。

2.2、非阻塞I/O

非阻塞IO通過進(jìn)程反復(fù)調(diào)用IO函數(shù)(多次系統(tǒng)調(diào)用,并馬上返回);在數(shù)據(jù)拷貝的過程中,進(jìn)程是阻塞的

我們把一個(gè)SOCKET接口設(shè)置為非阻塞就是告訴內(nèi)核,當(dāng)所請(qǐng)求的I/O操作無法完成時(shí),不要將進(jìn)程睡眠,而是返回一個(gè)錯(cuò)誤。這樣我們的I/O操作函數(shù)將不斷的測(cè)試數(shù)據(jù)是否已經(jīng)準(zhǔn)備好,如果沒有準(zhǔn)備好,繼續(xù)測(cè)試,直到數(shù)據(jù)準(zhǔn)備好為止。在這個(gè)不斷測(cè)試的過程中,會(huì)大量的占用CPU的時(shí)間。

2.3、IO復(fù)用

主要是select和epoll;對(duì)一個(gè)IO端口,兩次調(diào)用,兩次返回,比阻塞IO并沒有什么優(yōu)越性;關(guān)鍵是能實(shí)現(xiàn)同時(shí)對(duì)多個(gè)IO端口進(jìn)行監(jiān)聽;?I/O復(fù)用模型會(huì)用到select、poll、epoll函數(shù),這幾個(gè)函數(shù)也會(huì)使進(jìn)程阻塞,但是和阻塞I/O所不同的的,這兩個(gè)函數(shù)可以同時(shí)阻塞多個(gè)I/O操作。而且可以同時(shí)對(duì)多個(gè)讀操作,多個(gè)寫操作的I/O函數(shù)進(jìn)行檢測(cè),直到有數(shù)據(jù)可讀或可寫時(shí),才真正調(diào)用I/O操作函數(shù)。

?2.4、異步IO

當(dāng)一個(gè)異步過程調(diào)用發(fā)出后,調(diào)用者不能立刻得到結(jié)果。實(shí)際處理這個(gè)調(diào)用的部件在完成后,通過狀態(tài)、通知和回調(diào)來通知調(diào)用者的輸入輸出操作

最后,總結(jié)比較下五種IO模型:


最后,再舉幾個(gè)不是很恰當(dāng)?shù)睦觼碚f明這四個(gè)IO Model:
有A,B,C,D四個(gè)人在釣魚:
A用的是最老式的魚竿,所以呢,得一直守著,等到魚上鉤了再拉桿;
B的魚竿有個(gè)功能,能夠顯示是否有魚上鉤,所以呢,B就和旁邊的MM聊天,隔會(huì)再看看有沒有魚上鉤,有的話就迅速拉桿;
C用的魚竿和B差不多,但他想了一個(gè)好辦法,就是同時(shí)放好幾根魚竿,然后守在旁邊,一旦有顯示說魚上鉤了,它就將對(duì)應(yīng)的魚竿拉起來;
D是個(gè)有錢人,干脆雇了一個(gè)人幫他釣魚,一旦那個(gè)人把魚釣上來了,就給D發(fā)個(gè)短信。
?

總結(jié)

以上是生活随笔為你收集整理的IO:同步,异步,阻塞,非阻塞的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。