124 IO模型

1、IO模型介紹

回顧:同步、異步、阻塞、非阻塞python

同步: 指的是協同步調。既然叫協同,因此至少要有2個以上的事物存在。協同的結果就是:多個事物不能同時進行,必須一個一個的來,上一個事物結束後,下一個事物纔開始。linux

異步:就是步調各異,就是多個事物,你進行你的,我進行個人,誰都不用管誰,全部的事物都在同時進行中ios

總結:同步就是多個事物不能同時開工,異步就是多個事物能夠同時開工。程序員

阻塞:因爲執行任務過程當中遇到了阻塞,致使任務執行不了,處於等待的狀態web

非阻塞:就是執行任務過程當中沒有遇到阻塞,進而能夠一直執行任務數據庫

總結:回到程序裏,阻塞一樣意味着停下來等待,非阻塞代表能夠繼續向下執行。編程

1.阻塞I/O模型(blocking I/O)

2.非阻塞I/O模型(noblocking I/O)

3.I/O多道複用(I/O multiplexing)

4.信號驅動I/O(signal driven I/O)

5.異步I/O(asynchronous I/O)

前四個I/O都被稱之爲同步I/Owindows

I/O發生時涉及的對象和步驟:對於一個network I/O (網絡I/O)(這裏咱們以read舉例),它會涉及到兩個系統對象,一個是調用這個I/O的process (or thread),另外一個就是系統內核(kernel)。當一個read操做發生時,該操做會經歷兩個階段:緩存

  1. 等待數據準備 (Waiting data )
  2. 將數據從內核拷貝到進程中(Copying data )

注意:以上兩點和重要,由於在不一樣的I/O模型中,在以上兩個階段各有不一樣的狀況tomcat

2、阻塞I/O模型

在linux中,默認狀況下全部的socket都是blocking,一個典型的讀操做流程大概是這樣:

1569151255751

簡單理解:就是用戶進程在給服務端發送數據請求時,服務端可能不能當即給出返回數據,服務端要等待數據,這就形成了用戶進程一直在原地等着,直到服務端數據準備好了,在將數據從本身內存中拷貝到用戶內存中,而後用戶進程接着執行作同日他的操做。

阻塞I/O特色:blocking I/O的特色就是在I/O執行的兩個階段(等待數據和拷貝數據兩個階段)都被block了。

實際上:幾乎全部的I/O接口 ( 包括socket接口 ) 都是阻塞型的。這給網絡編程帶來了一個很大的問題,如在調用recv(1024)的同時,線程將被阻塞,在此期間,線程將沒法執行任何運算或響應任何的網絡請求。

2.1 解決方法(啓用多線程/多進程)

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

2.2 該方法的問題

開啓多進程或都線程的方式,在遇到要同時響應成百上千路的鏈接請求,則不管多線程仍是多進程都會嚴重佔據系統資源,下降系統對外界響應效率,並且線程與進程自己也更容易進入假死狀態。

2.3 改進方案(線程池/進程池)

不少程序員可能會考慮使用「線程池」或「鏈接池」。「線程池」旨在減小建立和銷燬線程的頻率,其維持必定合理數量的線程,並讓空閒的線程從新承擔新的執行任務。「鏈接池」維持鏈接的緩存池,儘可能重用已有的鏈接、減小建立和關閉鏈接的頻率。這兩種技術均可以很好的下降系統開銷,都被普遍應用不少大型系統,如websphere、tomcat和各類數據庫等。

2.4 改進後方案的問題

「線程池」和「鏈接池」技術也只是在必定程度上緩解了頻繁調用I/O接口帶來的資源佔用。並且,所謂「池」始終有其上限,當請求大大超過上限時,「池」構成的系統對外界的響應並不比沒有池的時候效果好多少。因此使用「池」必須考慮其面臨的響應規模,並根據響應規模調整「池」的大小。

對應上例中的所面臨的可能同時出現的上千甚至上萬次的客戶端請求,「線程池」或「鏈接池」或能夠緩解部分壓力,可是不能解決全部問題。總之,多線程模型能夠方便高效的解決小規模的服務請求,但面對大規模的服務請求,多線程模型也會遇到瓶頸,能夠用非阻塞接口來嘗試解決這個問題。

3、非阻塞I/O模型

從圖中能夠看出,當用戶進程向服務端發出請求數據時,若是服務端沒有數據會當即給用戶進程發送沒有數據,此時,因而用戶就能夠在本次到下次再發起read詢問的時間間隔內作其餘事情,或者直接再次發送read操做。一旦服務端中的數據準備好了,而且又再次收到了用戶進程的系統調用,那麼它立刻就將數據拷貝到了用戶內存(這一階段仍然是阻塞的),而後返回。

也就是說非阻塞的用戶京城recvform系統調用調用以後,進程並無被阻塞,內核立刻返回給進程,若是數據還沒準備好,此時會返回一個error。進程在返回以後,能夠乾點別的事情,而後再發起recvform系統調用。重複上面的過程,循環往復的進行recvform系統調用。這個過程一般被稱之爲輪詢。輪詢檢查內核數據,直到數據準備好,再拷貝數據到進程,進行數據處理。須要注意,拷貝數據整個過程,進程仍然是屬於阻塞的狀態。因此,在非阻塞式I/O中,用戶進程實際上是須要不斷的主動詢問服務端數據準備好了沒有。

3.1 非阻塞I/O實例

#服務端
import socket

server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind(('127.0.0.1',8080))
server.listen()
server.setblocking(False)#設置socket的接口爲非阻塞,只要沒人連服務端就報錯

conn_lis = []
del_conn = []
while True:
    try: # 只要沒客戶端連就報錯,因此捕捉異常
        conn,addr = server.accept()
        conn.lis.append(conn)
    except:
        for conn in conn_lis:
            try:# recv也是一個阻塞階段,只要沒人發數據就報錯,因此捕捉異常
                msg = conn.recv(1024)
                if msg == b'':
                    del_conn.append(conn)
                    continue
                 print(msg)
                conn.send(b'byebye')
             except:pass
         for conn in del_conn:
            conn_lis.remove(conn)
            conn.close()
         del_conn.clear()
#客戶端
import socket
import time
from threading import thread
def action():
    while True:
        client = socket.socket()
        client.connect(('127.0.0.1',8080))
        client.send(b'hello')
        time.sleep(2)
        client.recv(1024)
        print(msg)
        client.close()
            
for i in range(20):
    t  = thread(target=action)
    t.start()

重要非阻塞I/O模型毫不被推薦。

雖然可以在等待任務完成的時間裏幹其餘活了(包括提交其餘任務,也就是 「後臺」 能夠有多個任務在「」同時「」執行)。

  1. 可是咱們輪詢的去調用recv會大大的提升cpu的佔用率,致使效率變慢

  2. 任務完成的響應延遲增大了,由於每過一段時間纔去輪詢一次read操做,而任務可能在兩次輪詢之間的任意時間完成。這會致使總體數據吞吐量的下降。

4、I/O多路複用

I/O multiplexing(I/O多路複用)用到了select/poll模塊,是操做系統提供的一種機制,select/poll的好處就在於單個用戶process就能夠同時處理多個網絡鏈接的I/O。它的基本原理就是select/poll會不斷的輪詢所負責的全部socket,當某個socket有數據到達了,就通知用戶進程。它的流程如圖:

當用戶進程調用了select,那麼整個進程會被阻塞,而同時,服務端會「監視」全部select負責的socket,當任何一個socket中的數據準備好了,select就會返回。這個時候用戶進程再調用read操做,將數據從操做系統拷貝到用戶進程。

這個圖和blocking I/O(阻塞I/O)的圖其實並無太大的不一樣,事實上還更差一些。由於這裏須要使用兩個系統調用(select和recvfrom),而blocking I/O只調用了一個系統調用(recvfrom)。可是,用select的優點在於它能夠同時處理多個鏈接。

注意

  1. 若是處理的鏈接數不是很高的話,使用select/poll的web server不必定比使用multi-threading + blocking I/O的web server性能更好,可能延遲還更大。select/poll的優點並非對於單個鏈接能處理得更快,而是在於能處理更多的鏈接。

  2. 在多路複用模型中,對於每個socket,通常都設置成爲non-blocking,可是,如上圖所示,整個用戶的process實際上是一直被block的。只不過process是被select這個函數block,而不是被socket I/O給block。

結論: select的優點在於能夠處理多個鏈接,不適用於單個鏈接

4.1 select網絡I/O模型

#服務端
import socket
import select

server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind(('127.0.0.1',8080))
server.listen()
server.setblocking(False)#設置socket的接口爲非阻塞,只要沒人連服務端就報錯

re_lis = [server]
while True:
    r_lis,w_lis,x_lis = select.select(re_lis,[],[])
    for i in r_lis:
        if i == server:
            conn,addr = server.accept()
            re_lis.append(conn)
         else:
            try:
                msg = conn.recv(1024)
                if msg == b'':
                    conn.close()
                    re_lis.remove(conn)
                    continue
                print(msg)
                conn.send(b'byebye')
             except:pass
#客戶端
import socket
import time
from threading import thread
def action():
    while True:
        client = socket.socket()
        client.connect(('127.0.0.1',8080))
        client.send(b'hello')
        time.sleep(2)
        client.recv(1024)
        print(msg)
        client.close()
            
for i in range(20):
    t  = thread(target=action)
    t.start()

4.2 epoll模型

select只在windows系統下用

pool能夠在windows,linux下用

epoll只在linux下用

select/pool這兩種機制,用法都是同樣的:
但實際上這兩種方法雖然都是能夠解決咱們的阻塞,因爲這兩種方法也都是利用了輪詢的方式去處理阻塞的問題,因此當有不少個用戶同時來訪問服務端的時候,可能我第一個鏈接對象沒有給我發送請求的時候,我順延的執行了第二個鏈接對象的而請求,而後一直順延下去,當有10000個鏈接對象的話,我就要等到10000個鏈接對象都執行完了,我才能回過頭去處理第一個鏈接隊形的請求
因此select和pool這種方法都由於輪詢的緣由形成了執行任務的效率下降

epoll模型因爲它只能在linux下才能用,因此對咱們python來講是比較惋惜的可是咱們python中也有一種機制叫作selectors 它能夠幫咱們自動選擇該用那種模型,它其實是經過回調函數來解決的

5、信號驅動I/O模型(瞭解)

以買票的例子舉例,該模型小結爲:

# 老王去火車站買票,給售票員留下電話,有票後,售票員電話通知老王,而後老王去火車站交錢領票。

# 耗費:往返車站2次,路上2小時,免黃牛費100元,無需打電話

188-大白話五種IO模型-07.png?x-oss-process=style/watermark

因爲信號驅動I/O在實際中並不經常使用,因此咱們只作簡單瞭解。

信號驅動I/O模型,應用進程告訴內核:當數據報準備好的時候,給我發送一個信號,對SIGI/O信號進行捕捉,而且調用個人信號處理函數來獲取數據報。

6、異步I/O模型

以買票的例子舉例,該模型小結爲:

# 老王去火車站買票,給售票員留下電話,有票後,售票員電話通知老王並快遞送票上門。

# 耗費:往返車站1次,路上1小時,免黃牛費100元,無需打電話

Linux下的asynchronous I/O其實用得很少,從內核2.6版本纔開始引入。先看一下它的流程:

188-大白話五種IO模型-05.png?x-oss-process=style/watermark

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

7、I/O模型比較分析

阻塞 vs 非阻塞:

調用 阻塞I/O 會一直block住對應的進程直到操做完成,

非阻塞I/O 在kernel還準備數據的狀況下會馬上返回。

同步I/O 和 異步I/O 的區別:

定義

同步I/O操做數/O n致使請求進程被阻塞,直到I/O操做數/O n完成爲止;

異步I/O操做數/O n不會致使請求進程被阻塞;

區別

同步I/O 作」I/O operatI/O n」的時候會將process阻塞,按照這個定義,四個I/O模型能夠分爲兩大類,以前所述的 阻塞I/O , 非阻塞I/O ,I/O多路複用 都屬於 同步I/O 這一類,而 異步I/O 屬於後一類。

有人可能會說, 非阻塞I/O 並無被block啊。這裏有個很是「狡猾」的地方,定義中所指的」I/O operatI/O n」是指真實的I/O操做,就是例子中的recvfrom這個system call。 非阻塞I/O 在執行recvfrom這個system call的時候,若是kernel的數據沒有準備好,這時候不會block進程。可是,當kernel中數據準備好的時候,recvfrom會將數據從kernel拷貝到用戶內存中,這個時候進程是被block了,在這段時間內,進程是被block的。

異步I/O :當進程發起I/O操做以後,就直接返回不再理睬了,直到kernel發送一個信號,告訴進程說I/O完成。在這整個過程當中,進程徹底沒有被block。

各個I/O Model的比較如圖所示:

188-大白話五種IO模型-06.png?x-oss-process=style/watermark

通過上面的介紹,會發現 非阻塞I/O 和 異步I/O 的區別仍是很明顯的。在非阻塞I/O 中,雖然進程大部分時間都不會被block,可是它仍然要求進程去主動的check,而且當數據準備完成之後,也須要進程主動的再次調用recvfrom來將數據拷貝到用戶內存。而 異步I/O 則徹底不一樣。它就像是用戶進程將整個I/O操做交給了他人(kernel)完成,而後他人作完後發信號通知。在此期間,用戶進程不須要去檢查I/O操做的狀態,也不須要主動的去拷貝數據。

能夠看出,以上五個模型的阻塞程度由低到高爲:阻塞I/O>非阻塞I/O>多路轉接I/O>信號驅動I/O>異步I/O,所以他們的效率是由低到高的。

相關文章
相關標籤/搜索