標籤:upper 非阻塞 導致 lse error: ioerror print 大量 逾時
IO模型
1、阻塞IO2、非阻塞IO3、多工IO4、非同步IO
一、阻塞IO
blocking IO的特點就是在IO執行的兩個階段(等待資料和拷貝資料兩個階段)都被block了。
實際上,除非特別指定,幾乎所有的IO介面 ( 包括socket介面 ) 都是阻塞型的。
所謂阻塞型介面是指系統調用(一般是IO介面)不返回調用結果並讓當前線程一直阻塞,只有當該系統調用獲 得結果或者逾時出錯時才返回。
在linux中,預設情況下所有的socket都是blocking,一個典型的讀操作流程大概是這樣:
二、非阻塞IO(不推薦使用)一直不斷的詢問
Linux下,可以通過設定socket使其變為non-blocking。當對一個non-blocking socket執行讀操作時,流程是這個樣子:
可以看出,當使用者進程發出read操作時,如果kernel中的資料還沒有準備好,那麼它並不會block使用者進程,而是立刻返回一個error。從使用者進程角度講 ,它發起一個read操作後,並不需要等待,而是馬上就得到了一個結果。使用者進程判斷結果是一個error時,它就知道資料還沒有準備好,於是使用者就可以在本次到下次再發起read詢問的時間間隔內做其他事情,或者直接再次發送read操作。一旦kernel中的資料準備好了,並且又再次收到了使用者進程的system call,那麼它馬上就將資料拷貝到了使用者記憶體(這一階段仍然是阻塞的),然後返回。
在非阻塞式IO中,使用者進程其實是需要不斷的主動詢問kernel資料準備好了沒有。
# 服務端import socketimport timeserver=socket.socket()server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)server.bind((‘127.0.0.1‘,8083))server.listen(5)server.setblocking(False)r_list=[]w_list={}while 1: try: conn,addr=server.accept() r_list.append(conn) except BlockingIOError: # 強調強調強調:!!!非阻塞IO的精髓在於完全沒有阻塞!!! # time.sleep(0.5) # 開啟該行注釋純屬為了方便查看效果 print(‘在做其他的事情‘) print(‘rlist: ‘,len(r_list)) print(‘wlist: ‘,len(w_list)) # 遍曆讀列表,依次取出通訊端讀取內容 del_rlist=[] for conn in r_list: try: data=conn.recv(1024) if not data: conn.close() del_rlist.append(conn) continue w_list[conn]=data.upper() except BlockingIOError: # 沒有收成功,則繼續檢索下一個通訊端的接收 continue except ConnectionResetError: # 當前通訊端出異常,則關閉,然後加入刪除列表,等待被清除 conn.close() del_rlist.append(conn) # 遍曆寫列表,依次取出通訊端發送內容 del_wlist=[] for conn,data in w_list.items(): try: conn.send(data) del_wlist.append(conn) except BlockingIOError: continue # 清理無用的通訊端,無需再監聽它們的IO操作 for conn in del_rlist: r_list.remove(conn) for conn in del_wlist: w_list.pop(conn)
非阻塞IO的精髓在於完全沒有阻塞!!!
優:
能夠在等待任務完成的時間裡幹其他活了(包括提交其他任務,也就是 “後台” 可以有多個任務在“”同時“”執行)。
缺:
1. 迴圈調用recv()將大幅度推高CPU佔用率;這也是我們在代碼中留一句time.sleep(2)的原因,否則在低配主機下極容易出現卡機情況
2. 任務完成的響應延遲增大了,因為每過一段時間才去輪詢一次read操作,而任務可能在兩次輪詢之間的任意時間完成。這會導致整體資料輸送量的降低。
此外,在這個方案中recv()更多的是起到檢測“操作是否完成”的作用,實際作業系統提供了更為高效的檢測“操作是否完成“作用的介面,例如select()多工模式,可以一次檢測多個串連是否活躍。
三、多工IO(IO multiplexing) select幫它詢問,集中處理
r1,wl,x1=select.select(r_list,w_list,[],0.5) """ rlist -- wait until ready for reading wlist -- wait until ready for writing xlist -- wait for an ``exceptional condition‘‘ If only one kind of condition is required, pass [] for the other lists. """
IO multiplexing這個詞可能有點陌生,但是如果我說select/epoll,大概就都能明白了。有些地方也稱這種IO方式為事件驅動IO(event driven IO)。我們都知道,select/epoll的好處就在於單個process就可以同時處理多個網路連接的IO。它的基本原理就是select/epoll這個function會不斷的輪詢所負責的所有socket,當某個socket有資料到達了,就通知使用者進程。它的流程
當使用者進程調用了select,那麼整個進程會被block,而同時,kernel會“監視”所有select負責的socket,當任何一個socket中的資料準備好了,select就會返回。這個時候使用者進程再調用read操作,將資料從kernel拷貝到使用者進程。
這個圖和blocking IO的圖其實並沒有太大的不同,事實上還更差一些。因為這裡需要使用兩個系統調用(select和recvfrom),而blocking IO只調用了一個系統調用(recvfrom)。但是,用select的優勢在於它可以同時處理多個connection。
強調:
1. 如果處理的串連數不是很高的話,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server效能更好,可能延遲還更大。select/epoll的優勢並不是對於單個串連能處理得更快,而是在於能處理更多的串連。
2. 在多工模型中,對於每一個socket,一般都設定成為non-blocking,但是,如所示,整個使用者的process其實是一直被block的。只不過process是被select這個函數block,而不是被socket IO給block。
結論: select的優勢在於可以處理多個串連,不適用於單個串連
#服務端from socket import *import selectserver = socket(AF_INET, SOCK_STREAM)server.bind((‘127.0.0.1‘,8093))server.listen(5)server.setblocking(False)print(‘starting...‘)rlist=[server,]wlist=[]wdata={}while True: rl,wl,xl=select.select(rlist,wlist,[],0.5) print(wl) for sock in rl: if sock == server: conn,addr=sock.accept() rlist.append(conn) else: try: data=sock.recv(1024) if not data: sock.close() rlist.remove(sock) continue wlist.append(sock) wdata[sock]=data.upper() except Exception: sock.close() rlist.remove(sock) for sock in wl: sock.send(wdata[sock]) wlist.remove(sock) wdata.pop(sock)
select監聽fd變化的過程分析:
#使用者進程建立socket對象,拷貝監聽的fd到核心空間,每一個fd會對應一張系統檔案表,核心空間的fd響應到資料後,就會發送訊號給使用者進程資料已到;#使用者進程再發送系統調用,比如(accept)將核心空間的資料copy到使用者空間,同時作為接受資料端核心空間的資料清除,這樣重新監聽時fd再有新的資料又可以響應到了(發送端因為基於TCP協議所以需要收到應答後才會清除)。
優:
相比其他模型,使用select() 的事件驅動模型只用單線程(進程)執行,佔用資源少,不消耗太多 CPU,同時能夠為多用戶端提供服務。
如果試圖建立一個簡單的事件驅動的伺服器程式,這個模型有一定的參考價值。
缺:
1、首先select()介面並不是實現“事件驅動”的最好選擇。因為當需要探測的控制代碼值較大時,select()介面本身需要消耗大量時間去輪詢各個控制代碼。
很多作業系統提供了更為高效的介面,
2、其次,該模型將事件探測和事件響應夾雜在一起,一旦事件響應的執行體龐大,則對整個模型是災難性的。
四、非同步IO(Asynchronous I/O)
Linux下的asynchronous IO其實用得不多,從核心2.6版本才開始引入。先看一下它的流程:
使用者進程發起read操作之後,立刻就可以開始去做其它的事。而另一方面,從kernel的角度,當它受到一個asynchronous read之後,首先它會立刻返回,所以不會對使用者進程產生任何block。然後,kernel會等待資料準備完成,然後將資料拷貝到使用者記憶體,當這一切都完成之後,kernel會給使用者進程發送一個signal,告訴它read操作完成了。
純種的非同步IO 把 ‘ 從核心拷貝資料到進程 ’ 這個任務交給了核心來做,進程只會被通知數據到達
六、 IO模型比較分析
到目前為止,已經將四個IO Model都介紹完了。現在回過頭來回答最初的那幾個問題:blocking和non-blocking的區別在哪,synchronous IO和asynchronous IO的區別在哪。
先回答最簡單的這個:blocking vs non-blocking。前面的介紹中其實已經很明確的說明了這兩者的區別。調用blocking IO會一直block住對應的進程直到操作完成,而non-blocking IO在kernel還準備資料的情況下會立刻返回。
再說明synchronous IO和asynchronous IO的區別之前,需要先給出兩者的定義。Stevens給出的定義(其實是POSIX的定義)是這樣子的:
A synchronous I/O operation causes the requesting process to be blocked until that I/O operationcompletes;
An asynchronous I/O operation does not cause the requesting process to be blocked;
兩者的區別就在於synchronous IO做”IO operation”的時候會將process阻塞。按照這個定義,四個IO模型可以分為兩大類,之前所述的blocking IO,non-blocking IO,IO multiplexing都屬於synchronous IO這一類,而 asynchronous I/O後一類 。
有人可能會說,non-blocking IO並沒有被block啊。這裡有個非常“狡猾”的地方,定義中所指的”IO operation”是指真實的IO操作,就是例子中的recvfrom這個system call。non-blocking IO在執行recvfrom這個system call的時候,如果kernel的資料沒有準備好,這時候不會block進程。但是,當kernel中資料準備好的時候,recvfrom會將資料從kernel拷貝到使用者記憶體中,這個時候進程是被block了,在這段時間內,進程是被block的。而asynchronous IO則不一樣,當進程發起IO 操作之後,就直接返回再也不理睬了,直到kernel發送一個訊號,告訴進程說IO完成。在這整個過程中,進程完全沒有被block。
各個IO Model的比較:
經過上面的介紹,會發現non-blocking IO和asynchronous IO的區別還是很明顯的。在non-blocking IO中,雖然進程大部分時間都不會被block,但是它仍然要求進程去主動的check,並且當資料準備完成以後,也需要進程主動的再次調用recvfrom來將資料拷貝到使用者記憶體。而asynchronous IO則完全不同。它就像是使用者進程將整個IO操作交給了他人(kernel)完成,然後他人做完後發訊號通知。在此期間,使用者進程不需要去檢查IO操作的狀態,也不需要主動的去拷貝資料。
5月2日 python學習總結 IO模型