五種IO/模型

一 .IO模型

1.IO模型簡介

http://www.javashuo.com/article/p-mfckchsg-ea.htmlhtml

https://www.cnblogs.com/Eva-J/articles/8324837.htmllinux

同步(synchronous) I/O和異步(asynchronous) I/O,阻塞(blocking) I/O和非阻塞(non-blocking)I/O分別是什麼,到底有什麼區別?這個問題其
實不一樣的人給出的答案均可能不一樣,
好比wiki,就認爲asynchronous I/O和non-blocking I/O是一個東西。這實際上是由於不一樣的人的知識背景不一樣,而且在討論這個問題的時候上下文(context)也不相同。因此,爲了更好的回答這個問題,
我先限定一下本文的上下文。
住這兩點很重要,由於這些I/O模型的區別就是在兩個階段上各有不一樣的狀況。

在網絡環境下,再通俗的講,將I/O分爲兩步:

等;
數據搬遷。

若是要想提升I
/O效率,須要將等的時間下降。 五種I/O模型包括:阻塞I/O、非阻塞I/O、信號驅動I/O、I/O多路轉接、異步I/O。其中,前四個被稱爲同步I/O。

2. 阻塞IO模型

通俗理解阻塞Io模型 以買票的例子舉例,該模型小結爲

# 老王去火車站買票,排隊三天買到一張退票。

# 耗費:在車站吃喝拉撒睡 3天,其餘事一件沒幹


                                                                             阻塞IO模型圖

                             

當用戶進程調用了recvfrom這個系統調用,kernel就開始了I/O的第一個階段:準備數據。對於network I/O來講,不少時候數據在一開始尚未到達(好比,尚未收到一個完整的UDP包)
這個時候kernel就要等待足夠的數據到來。 而在用戶進程這邊,整個進程會被阻塞。當kernel一直等到數據準備好了,它就會將數據從kernel中拷貝到用戶內存,而後kernel返回結果,用戶進程才解除block的狀態
從新運行起來。因此,
blocking I/O的特色就是在I/O執行的兩個階段

(等待數據和拷貝數據兩個階段)都被block了。
幾乎全部的程序員第一次接觸到的網絡編程都是從listen()、send()、recv() 等接口開始的,使用這些接口能夠很方便的構建服務器/客戶機的模型。然而大部分的socket接口都是阻塞型的。以下圖

所謂阻塞型接口是指系統調用(通常是I/O接口)不返回調用結果並讓當前線程一直阻塞,只有當該系統調用得到結果或者超時出錯時才返回
1.簡單的解決方案 (這個方法有一些弊端)

在服務器端使用多線程(或多進程)。多線程(或多進程)的目的是讓每一個鏈接都擁有獨立的線程(或進程),這樣任何一個鏈接的阻塞都不會影響其餘的鏈接

改進後方案的問題

1. 不少程序員可能會考慮使用「線程池」或「鏈接池」。「線程池」旨在減小建立和銷燬線程的頻率,其維持必定合理數量的線程,並讓空閒的線程從新承擔新的執行任務。「鏈接池」維持鏈接的緩存池,儘可能重用已有的鏈接、減小建立和關閉鏈接的頻率。程序員

    這兩種技術均可以很好的下降系統開銷,都被普遍應用不少大型系統,如websphere、tomcat和各類數據庫等。web

 

2.「線程池」和「鏈接池」技術也只是在必定程度上緩解了頻繁調用I/O接口帶來的資源佔用。並且,所謂「池」始終有其上限,當請求大大超過上限時,「池」構成的系統對外界數據庫

   的響應並不比沒有池的時候效果好多少因此使用「池」必須考慮其面臨的響應規模,並根據響應規模調整「池」的大小。編程

 
 

3. 對應上例中的所面臨的可能同時出現的上千甚至上萬次的客戶端請求,「線程池」或「鏈接池」或許能夠緩解部分壓力,可是不能解決全部問題。總之,緩存

   多線程模型能夠方便高效的解決小規模的服務請求,但面對大規模的服務請求,多線程模型也會遇到瓶頸,能夠用非阻塞接口來嘗試解決這個問題。tomcat

 

 3. 非阻塞IO模型

通俗理解非阻塞IO模型 以買票的例子舉例,該模型小結爲: # 老王去火車站買票,隔12小時去火車站問有沒有退票,三天(72個小時)後買到一張票。

# 耗費:往返車站6次,路上6小時,其餘時間作了好多事。

                                                                                                             非阻塞Io模型圖服務器

                                              

 

從圖中能夠看出,當用戶進程發出read操做時,若是kernel中的數據尚未準備好,那麼它並不會block用戶進程,而是馬上返回一個error。從用戶進程角度講 ,它發起一個read操做後,並不須要等待,
而是立刻就獲得了一個結果。用戶進程判斷結果是一個error時,它就知道數據尚未準備好,因而用戶就能夠在本次到下次再發起read詢問的時間間隔內作其餘事情,或者直接再次發送read操做。
一旦kernel中的數據準備好了,而且又再次收到了用戶進程的system call,那麼它立刻就將數據拷貝到了用戶內存(這一階段仍然是阻塞的),而後返回。 也就是說非阻塞的recvform系統調用調用以後,進程並無被阻塞,內核立刻返回給進程,若是數據還沒準備好,此時會返回一個error。進程在返回以後,
能夠乾點別的事情,而後再發起recvform系統調用。

重複上面的過程,循環往復的進行recvform系統調用。這個過程一般被稱之爲輪詢。輪詢檢查內核數據,直到數據準備好,再拷貝數據到進程,進行數據處理
須要注意,拷貝數據整個過程,進程仍然是屬於阻塞的狀態

因此,在非阻塞式I
/O中,用戶進程實際上是須要不斷的主動詢問kernel數據準備好了沒有
 因此,在非阻塞式IO中,用戶進程實際上是須要不斷的主動詢問kernel數據準備好了沒有

#
服務端 from socket import * import time s=socket(AF_INET,SOCK_STREAM) s.bind(('127.0.0.1',8080)) s.listen(5) s.setblocking(False) #設置socket的接口爲非阻塞 conn_l=[] del_l=[] while True: try: conn,addr=s.accept() conn_l.append(conn) except BlockingIOError: print(conn_l) for conn in conn_l: try: data=conn.recv(1024) if not data: del_l.append(conn) continue conn.send(data.upper()) except BlockingIOError: pass except ConnectionResetError: del_l.append(conn) for conn in del_l: conn_l.remove(conn) conn.close() del_l=[] #客戶端 from socket import * c=socket(AF_INET,SOCK_STREAM) c.connect(('127.0.0.1',8080)) while True: msg=input('>>: ') if not msg:continue c.send(msg.encode('utf-8')) data=c.recv(1024) print(data.decode('utf-8')) 非阻塞IO實例
可是非阻塞I/O模型毫不被推薦。

咱們不可否則其優勢:可以在等待任務完成的時間裏幹其餘活了(包括提交其餘任務,也就是 「後臺」 能夠有多個任務在「」同時「」執行)。 可是也難掩其缺點:
循環調用recv()大幅度推高CPU佔用率;這也是咱們在代碼中留一句time.sleep(
2)的緣由,不然在低配主機下極容易出現卡機狀況
任務完成的響應延遲增大了,由於每過一段時間纔去輪詢一次read操做,而任務可能在兩次輪詢之間的任意時間完成
這會致使總體數據吞吐量的下降。

此外,在這個方案中recv()更多的是起到檢測「操做是否完成」的做用,實際操做系統提供了更爲高效的檢測「操做是否完成「做用的接口,例如select()多路複用模式,能夠一次檢測多個鏈接是否活躍。

4.多路複用IO模型

I/O多路複用,I/O就是指的咱們網絡I/O,多路指多個TCP鏈接(或多個Channel),複用指複用一個或少許線程。
串起來理解就是不少個網絡I/O複用一個或少許的線程來處理這些鏈接

做用就是檢測多個seckot是否發生了變化
阻塞(setblocking(False))+io多路複用
通俗理解多路複用阻塞IO模型 (select/poll模型) 以買票的例子舉例,該模型小結爲:

#
老王去火車站買票,委託黃牛,而後每隔6小時電話黃牛詢問,黃牛三天內買到票,而後老王去火車站交錢領票。 # 耗費:往返車站2次,路上2小時,黃牛手續費100元,打電話17次

多路複用模型圖網絡

                 

 

當用戶進程調用了select,那麼整個進程會被block,而同時,kernel會「監視」全部select負責的socket,當任何一個socket中的數據準備好了,select就會返回。
這個時候用戶進程再調用read操做,將數據從kernel拷貝到用戶進程。 這個圖和blocking I/O的圖其實並無太大的不一樣,事實上還更差一些。由於這裏須要使用兩個系統調用(select和recvfrom),而blocking I/O只調用了一個系統調用(recvfrom)
可是,用select的優點在於它能夠同時處理多個connectI/On。
若是處理的鏈接數不是很高的話,使用select/poll的web server不必定比使用multi-threading + blocking I/O的web server性能更好,可能延遲還更大。
select/poll的優點並非對於單個鏈接能處理得更快,而是在於能處理更多的鏈接。
在多路複用模型中,對於每個socket,通常都設置成爲non-blocking,可是,如上圖所示,整個用戶的process實際上是一直被block的。

只不過process是被select這個函數block,而不是被socket I/O給block。 結論: select的優點在於能夠處理多個鏈接,不適用於單個鏈接
 
 
單線程併發

# 單線程 + 非阻塞 =IO多路複用
# 說別了就是利用IO等待 作別的事件

IO多路複用 檢測多個socket是否發生了變化 (是否鏈接成功 / 是否已經獲取數據 ) 可讀/可寫

import socket
import select client1=socket.socket() client1.setblocking(False) # 默認是阻塞 開啓非阻塞 try: client1.connect(("www.baidu.com",8700)) except BlockingIOError as e: pass client2=socket.socket() client2.setblocking(False) # 默認是阻塞 開啓非阻塞 try: client2.connect(("www.sogou.com",8700)) except BlockingIOError as e: pass client3=socket.socket() client3.setblocking(False) # 默認是阻塞 開啓非阻塞 try: client3.connect(("www.oldboyedu.com",8700)) except BlockingIOError as e: pass socket_list=[client1,client2,client3] conn_list=[client1,client2,client3] while True: rlist,wlist,elist=select.select(socket_list,conn_list,[],0.005) # 檢測這幾個socket發生了變化 # wlist 中表示鏈接成功的socket對象 sk表示[client1 ,client2,client3] for sk in wlist: if sk==client1: sk.sendall(b'GET /s?wd=lover HTTP/1.0\r\nhost:www.baidu.com\r\n\r\n') elif sk==client2: sk.sendall(b'GET /web?query=aa HTTP/1.0\r\nhost:www.sogou.com\r\n\r\n') elif sk==client3: sk.sendall(b'GET /s?wd=lover HTTP/1.0\r\nhost:www.oldboyedu.com\r\n\r\n') conn_list.remove(sk) # 表示上面三個鏈接誰鏈接成功了就移除掉 # rlist 是表示上面發送成功了的數據 返回 回來的結果 就是接收 for sk in rlist: chunk_list=[] while True: try: # 沒有阻塞 若是沒有數據回來(接收)就會報錯 因此作個異常處理 chunk=sk.recv(8096) if not chunk: break chunk_list.append(chunk) except BlockingIOError as e: break body=b''.join(chunk_list) print(body)# 表示 數據回來了 接收成功了 # sk.close() # 關閉鏈接 socket_list.remove(sk) # 移除鏈接 if not socket_list: # 表三個數據都回來了 就是接收完了 break

select 參數分析

4.1.3 該模型的優勢
相比其餘模型,使用select() 的事件驅動模型只用單線程(進程)執行,佔用資源少,不消耗太多 CPU,同時可以爲多客戶端提供服務。若是試圖創建一個簡單的事件驅動的服務器程序,
這個模型有必定的參考價值
4.1.4 該模型的缺點 首先select()接口並非實現「事件驅動」的最好選擇。由於當須要探測的句柄值較大時,select()接口自己須要消耗大量時間去輪詢各個句柄。 不少操做系統提供了更爲高效的接口,如linux提供了epoll,BSD提供了kqueue,Solaris提供了/dev/poll,…。 若是須要實現更高效的服務器程序,相似epoll這樣的接口更被推薦。遺憾的是不一樣的操做系統特供的epoll接口有很大差別, 因此使用相似於epoll的接口實現具備較好跨平臺能力的服務器會比較困難。 其次,該模型將事件探測和事件響應夾雜在一塊兒,一旦事件響應的執行體龐大,則對整個模型是災難性的。

 5. 異步IO模型

 
 
通俗理解異步阻塞IO模型 以買票的例子舉例,該模型小結爲:
# 老王去火車站買票,給售票員留下電話,有票後,售票員電話通知老王並快遞送票上門。 # 耗費:往返車站1次,路上1小時,免黃牛費100元,無需打電話

                             異步IO模型圖

                                                                

用戶進程發起read操做以後,馬上就能夠開始去作其它的事。而另外一方面,從kernel的角度,當它受到一個asynchronous read以後,首先它會馬上返回,因此不會對用戶進程產生任何block。
而後,kernel會等待數據準備完成,而後將數據拷貝到用戶內存,當這一切都完成以後,kernel會給用戶進程發送一個signal,告訴它read操做完成了。

 http://www.javashuo.com/article/p-mfckchsg-ea.html

相關文章
相關標籤/搜索