5月2日 python學習總結 IO模型

來源:互聯網
上載者:User

標籤: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模型

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.