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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

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

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

從網(wǎng)上看到一遍比較好的博客介紹阻塞和非阻塞IO,異步和同步IO的區(qū)別和各自的使用場(chǎng)景,雖然是從網(wǎng)絡(luò)套接字方面解析的,不過也是適合于對(duì)驅(qū)動(dòng)文件的操作,畢竟套接字的本質(zhì)也是一個(gè)文件描述符。

轉(zhuǎn)載內(nèi)容

本文討論的背景是Linux環(huán)境下的network IO。本文最重要的參考文獻(xiàn)是Richard Stevens的“UNIX? Network Programming Volume 1, Third Edition: The Sockets Networking ”,6.2節(jié)“I/O Models ”,Stevens在這節(jié)中詳細(xì)說明了各種IO的特點(diǎn)和區(qū)別,如果英文夠好的話,推薦直接閱讀。Stevens的文風(fēng)是有名的深入淺出,所以不用擔(dān)心看不懂。本文中的流程圖也是截取自參考文獻(xiàn)。

Stevens在文章中一共比較了五種IO Model:
* blocking IO
* nonblocking IO
* IO multiplexing
* signal driven IO
* asynchronous IO
由signal driven 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)
2)將數(shù)據(jù)從內(nèi)核拷貝到進(jìn)程中(Copying the data from the kernel to the process)
記住這兩點(diǎn)很重要,因?yàn)檫@些IO模型的區(qū)別就是在兩個(gè)階段上各有不同的情況。

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

圖1 阻塞IO

當(dāng)用戶進(jìn)程調(diào)用了recvfrom這個(gè)系統(tǒng)調(diào)用,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è)階段(等待數(shù)據(jù)和拷貝數(shù)據(jù)兩個(gè)階段)都被block了。

幾乎所有的程序員第一次接觸到的網(wǎng)絡(luò)編程都是從listen()、send()、recv() 等接口開始的,這些接口都是阻塞型的。使用這些接口可以很方便的構(gòu)建服務(wù)器/客戶機(jī)的模型。下面是一個(gè)簡(jiǎn)單地“一問一答”的服務(wù)器。


圖2 簡(jiǎn)單的一問一答的服務(wù)器/客戶機(jī)模型

我們注意到,大部分的socket接口都是阻塞型的。所謂阻塞型接口是指系統(tǒng)調(diào)用(一般是IO接口)不返回調(diào)用結(jié)果并讓當(dāng)前線程一直阻塞,只有當(dāng)該系統(tǒng)調(diào)用獲得結(jié)果或者超時(shí)出錯(cuò)時(shí)才返回。
實(shí)際上,除非特別指定,幾乎所有的IO接口 ( 包括socket接口 ) 都是阻塞型的。這給網(wǎng)絡(luò)編程帶來了一個(gè)很大的問題,如在調(diào)用send()的同時(shí),線程將被阻塞,在此期間,線程將無法執(zhí)行任何運(yùn)算或響應(yīng)任何的網(wǎng)絡(luò)請(qǐng)求。

一個(gè)簡(jiǎn)單的改進(jìn)方案是在服務(wù)器端使用多線程(或多進(jìn)程)。多線程(或多進(jìn)程)的目的是讓每個(gè)連接都擁有獨(dú)立的線程(或進(jìn)程),這樣任何一個(gè)連接的阻塞都不會(huì)影響其他的連接。具體使用多進(jìn)程還是多線程,并沒有一個(gè)特定的模式。傳統(tǒng)意義上,進(jìn)程的開銷要遠(yuǎn)遠(yuǎn)大于線程,所以如果需要同時(shí)為較多的客戶機(jī)提供服務(wù),則不推薦使用多進(jìn)程;如果單個(gè)服務(wù)執(zhí)行體需要消耗較多的CPU資源,譬如需要進(jìn)行大規(guī)模或長(zhǎng)時(shí)間的數(shù)據(jù)運(yùn)算或文件訪問,則進(jìn)程較為安全。通常,使用pthread_create ()創(chuàng)建新線程,fork()創(chuàng)建新進(jìn)程。
我們假設(shè)對(duì)上述的服務(wù)器 / 客戶機(jī)模型,提出更高的要求,即讓服務(wù)器同時(shí)為多個(gè)客戶機(jī)提供一問一答的服務(wù)。于是有了如下的模型。

圖3 多線程的服務(wù)器模型

在上述的線程 / 時(shí)間圖例中,主線程持續(xù)等待客戶端的連接請(qǐng)求,如果有連接,則創(chuàng)建新線程,并在新線程中提供為前例同樣的問答服務(wù)。
很多初學(xué)者可能不明白為何一個(gè)socket可以accept多次。實(shí)際上socket的設(shè)計(jì)者可能特意為多客戶機(jī)的情況留下了伏筆,讓accept()能夠返回一個(gè)新的socket。下面是 accept 接口的原型:
int accept(int s, struct sockaddr *addr, socklen_t *addrlen);
輸入?yún)?shù)s是從socket(),bind()和listen()中沿用下來的socket句柄值。執(zhí)行完bind()和listen()后,操作系統(tǒng)已經(jīng)開始在指定的端口處監(jiān)聽所有的連接請(qǐng)求,如果有請(qǐng)求,則將該連接請(qǐng)求加入請(qǐng)求隊(duì)列。調(diào)用accept()接口正是從 socket s 的請(qǐng)求隊(duì)列抽取第一個(gè)連接信息,創(chuàng)建一個(gè)與s同類的新的socket返回句柄。新的socket句柄即是后續(xù)read()和recv()的輸入?yún)?shù)。如果請(qǐng)求隊(duì)列當(dāng)前沒有請(qǐng)求,則accept() 將進(jìn)入阻塞狀態(tài)直到有請(qǐng)求進(jìn)入隊(duì)列。
上述多線程的服務(wù)器模型似乎完美的解決了為多個(gè)客戶機(jī)提供問答服務(wù)的要求,但其實(shí)并不盡然。如果要同時(shí)響應(yīng)成百上千路的連接請(qǐng)求,則無論多線程還是多進(jìn)程都會(huì)嚴(yán)重占據(jù)系統(tǒng)資源,降低系統(tǒng)對(duì)外界響應(yīng)效率,而線程與進(jìn)程本身也更容易進(jìn)入假死狀態(tài)。
很多程序員可能會(huì)考慮使用“線程池”或“連接池”。“線程池”旨在減少創(chuàng)建和銷毀線程的頻率,其維持一定合理數(shù)量的線程,并讓空閑的線程重新承擔(dān)新的執(zhí)行任務(wù)。“連接池”維持連接的緩存池,盡量重用已有的連接、減少創(chuàng)建和關(guān)閉連接的頻率。這兩種技術(shù)都可以很好的降低系統(tǒng)開銷,都被廣泛應(yīng)用很多大型系統(tǒng),如websphere、tomcat和各種數(shù)據(jù)庫等。但是,“線程池”和“連接池”技術(shù)也只是在一定程度上緩解了頻繁調(diào)用IO接口帶來的資源占用。而且,所謂“池”始終有其上限,當(dāng)請(qǐng)求大大超過上限時(shí),“池”構(gòu)成的系統(tǒng)對(duì)外界的響應(yīng)并不比沒有池的時(shí)候效果好多少。所以使用“池”必須考慮其面臨的響應(yīng)規(guī)模,并根據(jù)響應(yīng)規(guī)模調(diào)整“池”的大小。
對(duì)應(yīng)上例中的所面臨的可能同時(shí)出現(xiàn)的上千甚至上萬次的客戶端請(qǐng)求,“線程池”或“連接池”或許可以緩解部分壓力,但是不能解決所有問題。總之,多線程模型可以方便高效的解決小規(guī)模的服務(wù)請(qǐng)求,但面對(duì)大規(guī)模的服務(wù)請(qǐng)求,多線程模型也會(huì)遇到瓶頸,可以用非阻塞接口來嘗試解決這個(gè)問題。

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

圖4 非阻塞IO

從圖中可以看出,當(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)存,然后返回。
所以,在非阻塞式IO中,用戶進(jìn)程其實(shí)是需要不斷的主動(dòng)詢問kernel數(shù)據(jù)準(zhǔn)備好了沒有。

非阻塞的接口相比于阻塞型接口的顯著差異在于,在被調(diào)用之后立即返回。使用如下的函數(shù)可以將某句柄fd設(shè)為非阻塞狀態(tài)。
fcntl( fd, F_SETFL, O_NONBLOCK );
下面將給出只用一個(gè)線程,但能夠同時(shí)從多個(gè)連接中檢測(cè)數(shù)據(jù)是否送達(dá),并且接受數(shù)據(jù)的模型。

圖5 使用非阻塞的接收數(shù)據(jù)模型

在非阻塞狀態(tài)下,recv() 接口在被調(diào)用后立即返回,返回值代表了不同的含義。如在本例中,
* recv() 返回值大于 0,表示接受數(shù)據(jù)完畢,返回值即是接受到的字節(jié)數(shù);
* recv() 返回 0,表示連接已經(jīng)正常斷開;
* recv() 返回 -1,且 errno 等于 EAGAIN,表示 recv 操作還沒執(zhí)行完成;
* recv() 返回 -1,且 errno 不等于 EAGAIN,表示 recv 操作遇到系統(tǒng)錯(cuò)誤 errno。
可以看到服務(wù)器線程可以通過循環(huán)調(diào)用recv()接口,可以在單個(gè)線程內(nèi)實(shí)現(xiàn)對(duì)所有連接的數(shù)據(jù)接收工作。但是上述模型絕不被推薦。因?yàn)?#xff0c;循環(huán)調(diào)用recv()將大幅度推高CPU 占用率;此外,在這個(gè)方案中recv()更多的是起到檢測(cè)“操作是否完成”的作用,實(shí)際操作系統(tǒng)提供了更為高效的檢測(cè)“操作是否完成“作用的接口,例如select()多路復(fù)用模式,可以一次檢測(cè)多個(gè)連接是否活躍。

3、多路復(fù)用IO(IO multiplexing)
IO multiplexing這個(gè)詞可能有點(diǎn)陌生,但是如果我說select/epoll,大概就都能明白了。有些地方也稱這種IO方式為事件驅(qū)動(dòng)IO(event driven IO)。我們都知道,select/epoll的好處就在于單個(gè)process就可以同時(shí)處理多個(gè)網(wǎng)絡(luò)連接的IO。它的基本原理就是select/epoll這個(gè)function會(huì)不斷的輪詢所負(fù)責(zé)的所有socket,當(dāng)某個(gè)socket有數(shù)據(jù)到達(dá)了,就通知用戶進(jìn)程。它的流程如圖:

圖6 多路復(fù)用IO

當(dāng)用戶進(jìn)程調(diào)用了select,那么整個(gè)進(jìn)程會(huì)被block,而同時(shí),kernel會(huì)“監(jiān)視”所有select負(fù)責(zé)的socket,當(dāng)任何一個(gè)socket中的數(shù)據(jù)準(zhǔn)備好了,select就會(huì)返回。這個(gè)時(shí)候用戶進(jìn)程再調(diào)用read操作,將數(shù)據(jù)從kernel拷貝到用戶進(jìn)程。
這個(gè)圖和blocking IO的圖其實(shí)并沒有太大的不同,事實(shí)上還更差一些。因?yàn)檫@里需要使用兩個(gè)系統(tǒng)調(diào)用(select和recvfrom),而blocking IO只調(diào)用了一個(gè)系統(tǒng)調(diào)用(recvfrom)。但是,用select的優(yōu)勢(shì)在于它可以同時(shí)處理多個(gè)connection。(多說一句:所以,如果處理的連接數(shù)不是很高的話,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延遲還更大。select/epoll的優(yōu)勢(shì)并不是對(duì)于單個(gè)連接能處理得更快,而是在于能處理更多的連接。)
在多路復(fù)用模型中,對(duì)于每一個(gè)socket,一般都設(shè)置成為non-blocking,但是,如上圖所示,整個(gè)用戶的process其實(shí)是一直被block的。只不過process是被select這個(gè)函數(shù)block,而不是被socket IO給block。因此select()與非阻塞IO類似。

大部分Unix/Linux都支持select函數(shù),該函數(shù)用于探測(cè)多個(gè)文件句柄的狀態(tài)變化。下面給出select接口的原型:
FD_ZERO(int fd, fd_set* fds)
FD_SET(int fd, fd_set* fds)
FD_ISSET(int fd, fd_set* fds)
FD_CLR(int fd, fd_set* fds)
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
struct timeval *timeout)
這里,fd_set 類型可以簡(jiǎn)單的理解為按 bit 位標(biāo)記句柄的隊(duì)列,例如要在某 fd_set 中標(biāo)記一個(gè)值為16的句柄,則該fd_set的第16個(gè)bit位被標(biāo)記為1。具體的置位、驗(yàn)證可使用 FD_SET、FD_ISSET等宏實(shí)現(xiàn)。在select()函數(shù)中,readfds、writefds和exceptfds同時(shí)作為輸入?yún)?shù)和輸出參數(shù)。如果輸入的readfds標(biāo)記了16號(hào)句柄,則select()將檢測(cè)16號(hào)句柄是否可讀。在select()返回后,可以通過檢查readfds有否標(biāo)記16號(hào)句柄,來判斷該“可讀”事件是否發(fā)生。另外,用戶可以設(shè)置timeout時(shí)間。
下面將重新模擬上例中從多個(gè)客戶端接收數(shù)據(jù)的模型。

圖7 使用select()的接收數(shù)據(jù)模型

述模型只是描述了使用select()接口同時(shí)從多個(gè)客戶端接收數(shù)據(jù)的過程;由于select()接口可以同時(shí)對(duì)多個(gè)句柄進(jìn)行讀狀態(tài)、寫狀態(tài)和錯(cuò)誤狀態(tài)的探測(cè),所以可以很容易構(gòu)建為多個(gè)客戶端提供獨(dú)立問答服務(wù)的服務(wù)器系統(tǒng)。如下圖。

圖8 使用select()接口的基于事件驅(qū)動(dòng)的服務(wù)器模型

這里需要指出的是,客戶端的一個(gè) connect() 操作,將在服務(wù)器端激發(fā)一個(gè)“可讀事件”,所以 select() 也能探測(cè)來自客戶端的 connect() 行為。
上述模型中,最關(guān)鍵的地方是如何動(dòng)態(tài)維護(hù)select()的三個(gè)參數(shù)readfds、writefds和exceptfds。作為輸入?yún)?shù),readfds應(yīng)該標(biāo)記所有的需要探測(cè)的“可讀事件”的句柄,其中永遠(yuǎn)包括那個(gè)探測(cè) connect() 的那個(gè)“母”句柄;同時(shí),writefds 和 exceptfds 應(yīng)該標(biāo)記所有需要探測(cè)的“可寫事件”和“錯(cuò)誤事件”的句柄 ( 使用 FD_SET() 標(biāo)記 )。
作為輸出參數(shù),readfds、writefds和exceptfds中的保存了 select() 捕捉到的所有事件的句柄值。程序員需要檢查的所有的標(biāo)記位 ( 使用FD_ISSET()檢查 ),以確定到底哪些句柄發(fā)生了事件。
上述模型主要模擬的是“一問一答”的服務(wù)流程,所以如果select()發(fā)現(xiàn)某句柄捕捉到了“可讀事件”,服務(wù)器程序應(yīng)及時(shí)做recv()操作,并根據(jù)接收到的數(shù)據(jù)準(zhǔn)備好待發(fā)送數(shù)據(jù),并將對(duì)應(yīng)的句柄值加入writefds,準(zhǔn)備下一次的“可寫事件”的select()探測(cè)。同樣,如果select()發(fā)現(xiàn)某句柄捕捉到“可寫事件”,則程序應(yīng)及時(shí)做send()操作,并準(zhǔn)備好下一次的“可讀事件”探測(cè)準(zhǔn)備。下圖描述的是上述模型中的一個(gè)執(zhí)行周期。

圖9 多路復(fù)用模型的一個(gè)執(zhí)行周期

這種模型的特征在于每一個(gè)執(zhí)行周期都會(huì)探測(cè)一次或一組事件,一個(gè)特定的事件會(huì)觸發(fā)某個(gè)特定的響應(yīng)。我們可以將這種模型歸類為“事件驅(qū)動(dòng)模型”。
相比其他模型,使用select() 的事件驅(qū)動(dòng)模型只用單線程(進(jìn)程)執(zhí)行,占用資源少,不消耗太多 CPU,同時(shí)能夠?yàn)槎嗫蛻舳颂峁┓?wù)。如果試圖建立一個(gè)簡(jiǎn)單的事件驅(qū)動(dòng)的服務(wù)器程序,這個(gè)模型有一定的參考價(jià)值。
但這個(gè)模型依舊有著很多問題。首先select()接口并不是實(shí)現(xiàn)“事件驅(qū)動(dòng)”的最好選擇。因?yàn)楫?dāng)需要探測(cè)的句柄值較大時(shí),select()接口本身需要消耗大量時(shí)間去輪詢各個(gè)句柄。很多操作系統(tǒng)提供了更為高效的接口,如linux提供了epoll,BSD提供了kqueue,Solaris提供了/dev/poll,…。如果需要實(shí)現(xiàn)更高效的服務(wù)器程序,類似epoll這樣的接口更被推薦。遺憾的是不同的操作系統(tǒng)特供的epoll接口有很大差異,所以使用類似于epoll的接口實(shí)現(xiàn)具有較好跨平臺(tái)能力的服務(wù)器會(huì)比較困難。
其次,該模型將事件探測(cè)和事件響應(yīng)夾雜在一起,一旦事件響應(yīng)的執(zhí)行體龐大,則對(duì)整個(gè)模型是災(zāi)難性的。如下例,龐大的執(zhí)行體1的將直接導(dǎo)致響應(yīng)事件2的執(zhí)行體遲遲得不到執(zhí)行,并在很大程度上降低了事件探測(cè)的及時(shí)性。

圖10 龐大的執(zhí)行體對(duì)使用select()的事件驅(qū)動(dòng)模型的影響

幸運(yùn)的是,有很多高效的事件驅(qū)動(dòng)庫可以屏蔽上述的困難,常見的事件驅(qū)動(dòng)庫有l(wèi)ibevent庫,還有作為libevent替代者的libev庫。這些庫會(huì)根據(jù)操作系統(tǒng)的特點(diǎn)選擇最合適的事件探測(cè)接口,并且加入了信號(hào)(signal) 等技術(shù)以支持異步響應(yīng),這使得這些庫成為構(gòu)建事件驅(qū)動(dòng)模型的不二選擇。下章將介紹如何使用libev庫替換select或epoll接口,實(shí)現(xiàn)高效穩(wěn)定的服務(wù)器模型。

實(shí)際上,Linux內(nèi)核從2.6開始,也引入了支持異步響應(yīng)的IO操作,如aio_read, aio_write,這就是異步IO。

4、異步IO(Asynchronous I/O)
Linux下的asynchronous IO其實(shí)用得不多,從內(nèi)核2.6版本才開始引入。先看一下它的流程:

圖11 異步IO

用戶進(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操作完成了。

用異步IO實(shí)現(xiàn)的服務(wù)器這里就不舉例了,以后有時(shí)間另開文章來講述。異步IO是真正非阻塞的,它不會(huì)對(duì)請(qǐng)求進(jìn)程產(chǎn)生任何的阻塞,因此對(duì)高并發(fā)的網(wǎng)絡(luò)服務(wù)器實(shí)現(xiàn)至關(guān)重要。
到目前為止,已經(jīng)將四個(gè)IO模型都介紹完了。現(xiàn)在回過頭來回答最初的那幾個(gè)問題:blocking和non-blocking的區(qū)別在哪,synchronous IO和asynchronous IO的區(qū)別在哪。
先回答最簡(jiǎn)單的這個(gè):blocking與non-blocking。前面的介紹中其實(shí)已經(jīng)很明確的說明了這兩者的區(qū)別。調(diào)用blocking IO會(huì)一直block住對(duì)應(yīng)的進(jìn)程直到操作完成,而non-blocking IO在kernel還在準(zhǔn)備數(shù)據(jù)的情況下會(huì)立刻返回。
在說明synchronous IO和asynchronous IO的區(qū)別之前,需要先給出兩者的定義。Stevens給出的定義(其實(shí)是POSIX的定義)是這樣子的:
* A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes;
* An asynchronous I/O operation does not cause the requesting process to be blocked;
兩者的區(qū)別就在于synchronous IO做”IO operation”的時(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è)系統(tǒng)調(diào)用。non-blocking IO在執(zhí)行recvfrom這個(gè)系統(tǒng)調(diào)用的時(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的。而asynchronous IO則不一樣,當(dāng)進(jìn)程發(fā)起IO操作之后,就直接返回再也不理睬了,直到kernel發(fā)送一個(gè)信號(hào),告訴進(jìn)程說IO完成。在這整個(gè)過程中,進(jìn)程完全沒有被block。

還有一種不常用的signal driven IO,即信號(hào)驅(qū)動(dòng)IO。總的來說,UNP中總結(jié)的IO模型有5種之多:阻塞IO,非阻塞IO,IO復(fù)用,信號(hào)驅(qū)動(dòng)IO,異步IO。前四種都屬于同步IO。阻塞IO不必說了。非阻塞IO ,IO請(qǐng)求時(shí)加上O_NONBLOCK一類的標(biāo)志位,立刻返回,IO沒有就緒會(huì)返回錯(cuò)誤,需要請(qǐng)求進(jìn)程主動(dòng)輪詢不斷發(fā)IO請(qǐng)求直到返回正確。IO復(fù)用同非阻塞IO本質(zhì)一樣,不過利用了新的select系統(tǒng)調(diào)用,由內(nèi)核來負(fù)責(zé)本來是請(qǐng)求進(jìn)程該做的輪詢操作。看似比非阻塞IO還多了一個(gè)系統(tǒng)調(diào)用開銷,不過因?yàn)榭梢灾С侄嗦稩O,才算提高了效率。信號(hào)驅(qū)動(dòng)IO,調(diào)用sigaltion系統(tǒng)調(diào)用,當(dāng)內(nèi)核中IO數(shù)據(jù)就緒時(shí)以SIGIO信號(hào)通知請(qǐng)求進(jìn)程,請(qǐng)求進(jìn)程再把數(shù)據(jù)從內(nèi)核讀入到用戶空間,這一步是阻塞的。
異步IO,如定義所說,不會(huì)因?yàn)镮O操作阻塞,IO操作全部完成才通知請(qǐng)求進(jìn)程。
各個(gè)IO Model的比較如圖所示:

圖12 各種IO模型的比較

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

總結(jié)

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

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