IO模型--阻塞IO,非阻塞IO,IO多路複用,異步IO

IO模型介紹:html

* blocking IO 阻塞IO
* nonblocking IO 非阻塞IO
* IO multiplexing IO多路複用
* signal driven IO 信號驅動IO ()
* asynchronous IO 異步IOpython

 

IO模型介紹:


 爲了更好地瞭解IO模型,咱們須要事先回顧下:同步、異步、阻塞、非阻塞linux

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

    本文討論的背景是Linux環境下的network IO。本文最重要的參考文獻是Richard Stevens的「UNIX® Network Programming Volume 1, Third Edition: The Sockets Networking 」,6.2節「I/O Models 」,Stevens在這節中詳細說明了各類IO的特色和區別,若是英文夠好的話,推薦直接閱讀。Stevens的文風是有名的深刻淺出,因此不用擔憂看不懂。本文中的流程圖也是截取自參考文獻。windows

    Stevens在文章中一共比較了五種IO Model:
    * blocking IO           阻塞IO
    * nonblocking IO      非阻塞IO
    * IO multiplexing      IO多路複用
    * signal driven IO     信號驅動IO
    * asynchronous IO    異步IO
    由signal driven IO(信號驅動IO)在實際中並不經常使用,因此主要介紹其他四種IO Model。網絡

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

#1)等待數據準備 (Waiting for the data to be ready)
#2)將數據從內核拷貝到進程中(Copying the data from the kernel to the process)

  記住這兩點很重要,由於這些IO模型的區別就是在兩個階段上各有不一樣的狀況。框架

#同步:提交一個任務以後,要等待這個任務執行完畢
       #好比去銀行存錢,要取完號才能存錢
#異步:只管提交任務,不等待這個任務執行完畢,就能夠作其餘事情
        #能夠邊存錢,邊玩手機
#阻塞:recv recvfrom accpt    取號的時候,等待人不少
#非阻塞: #不少種狀況了


#recv 等待數據準備,等待數據從內核拷貝到進程
#send 講用戶要發送的信息copy到操做系統  發送的時候的網絡延遲
        #主動方因此阻塞很小
小理解

景老師筆記: IO多路複用異步

 

一.阻塞IO:socket

#圖在 有道詞典 20181001 IO模型
#1.tcp/udp recv的時候—系統調用—內核操做系統數據沒有準備好—等待數據------->數據準備好了,copy數據,將操做系統中的數據,複製,而且返回給進程
#兩個重要節點:1.數據準備,2.將信息從內核拷貝到進程

#阻塞IO特色:程序被阻塞着,接收不了其餘任務
#進程和線程,沒有解決阻塞的這段時間,只是在開啓多個進程/線程,該等仍是在等,每一個線程都受到了阻塞的影響
#協程只是必定程度上的解決這個問題,可是仍是在等待

 

二.非阻塞IO:

#圖在有道詞典20181001 IO模型

#非阻塞IO
#用戶端:
#recv ----> 告訴系統我要收數據了 ----> 系統告訴你沒數據---->而後程序沒有阻塞住,能夠繼續日後執行,可是也沒收到數據
#過段時間,繼續追問
#recv----> 而後系統繼續告訴你沒有數據,重複上面過程
#假設,一直在請求,請求了不少次,直到
#recv----> 有數據了,---->  從操做系統copy到進程中,而後告訴用戶,有數據了



# 可是非阻塞IO模型毫不被推薦。
#     咱們不可否則其優勢:可以在等待任務完成的時間裏幹其餘活了(包括提交其餘任務,也就是 「後臺」 能夠有多個任務在「」同時「」執行)。

# 可是也難掩其缺點:
    #1. 循環調用recv()將大幅度推高CPU佔用率;這也是咱們在代碼中留一句time.sleep(2)的緣由,不然在低配主機下極容易出現卡機狀況
    #2. 任務完成的響應延遲增大了,由於每過一段時間纔去輪詢一次read操做,而任務可能在兩次輪詢之間的任意時間完成。這會致使總體數據吞吐量的下降。
    #     此外,在這個方案中recv()更多的是起到檢測「操做是否完成」的做用,
    # 實際操做系統提供了更爲高效的檢測「操做是否完成「做用的接口,例如select()多路複用模式,能夠一次檢測多個鏈接是否活躍。

非阻塞IO框架,例子及解釋:

import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8088))
sk.setblocking(False) #~~~~~~~~~~~~~~將阻塞設置爲不阻塞
sk.listen()
conn_lst = []
del_lst = []
while True:
    try:
        conn,addr = sk.accept()
        print('創建了鏈接',addr)
        conn_lst.append(conn)
        # msg = conn.recv(1024).decode('utf8') #註釋1
        # print(msg)
    except BlockingIOError:
        for con in conn_lst: #循環反覆的獲取con
            try:
                msg = con.recv(1024)  # 這裏仍是非阻塞,仍是會報錯
                if msg == b'':                       # 可是若是一直從一個關閉的client獲取數據,會一直打印空
                    del_lst.append(con)  #不能 conn_lst.remove(con) 不能再for循環中列表裏的元素,index會錯誤
                    continue #當con傳過來爲空,就不執行後面的語句了,要進行下一個循環
                print(msg)
                con.send(b'bye')
            except BlockingIOError:pass
        for con in del_lst:
            con.close()
            conn_lst.remove(con)
        del_lst.clear()

#註釋1:
    #此處有兩種狀況,第一種因爲我此時是不阻塞的(setblocking)client端以很是快速的和我聊天,致使while循環就沒效果了,別人接收不了請求了
    #可是若是隻快速的發了一句,而後server就只能接收這一句,接下來就會由於非阻塞,而後這個conn就被內存給沖掉了
    #第二種因爲此時是阻塞的,client若是慢了一點給我傳消息,我立刻會跳過,而且報錯BlockingIOError

#解決方法:
    #創建一個conn列表,把每次鏈接成功的conn,放到列表裏。而且把recv放到 except語句後,由於沒信息就會報錯。
    #而後for循環這個conn列表,一直去嘗試獲取,conn是否發了消息過來
    #從而讓原本本內存刷掉的conn,能夠一直被嘗試獲取
    #可是此是for循環的列表裏,recv仍是非阻塞的,因此要繼續異常處理
server端
import time
import socket
import threading

def func():
    sk = socket.socket()
    sk.connect(('127.0.0.1', 8088))
    sk.send(b'hello')
    time.sleep(0.1)
    print(sk.recv(1024))
    sk.close()

for i in range(20):
    threading.Thread(target=func).start()
client端

 

 

三.IO多路複用:(select模塊)

#select 模塊
#圖在有道
# IO多路複用:操做系統級別的,windows的select機制,不是咱們代碼提供的

# 流程:
#1.有個代理(select模塊),能夠幫助你監聽一個對象。並且這個代理能夠監聽多個對象 conn1,2,3,4...
    #能夠接受數據的對象,好比 socket.accept   conn.recv
    #發生一次系統調用,
#2.而後這個代理,會替你等待 socket對象 被鏈接,若是沒有人來連,會一直阻塞  (從而監聽對象就能夠不阻塞了)
#3.阻塞到有client來鏈接,而後把 反饋信息 給幫忙監聽的對象,好比accept
    #2-3步驟,是操做系統幫你循環監聽列表,查看每一項是否有可讀事件(但不是很高效)

#4.而後accept知道了有數據來了,再次產生一次系統調用,找操做系統要數據
#5.系統就複製數據,傳給了一開始索要數據的對象

#注意: 1~3步驟纔是IO多路複用,4~5步就是 對象正常獲取數據的步驟了


#監聽方式:
    #windows 只有 select機制
    #linux :
        # select機制: 都是操做系統輪詢每個被監聽的對象,看是否有讀操做
        #poll機制: 和select機制同樣,可是poll好在能夠監聽的對象,比select機制多
                    #好比select能夠監聽500個,poll能夠監聽1000個
                    #隨着監聽項的增多,會致使監聽效率下降。(好比監聽列表有1000個,當我監聽完第3個了,第2個來消息了,)
        #epoll機制:
                    #給監聽的每個對象,都綁定了一個回調函數
                    #每當有可讀事件,回調函數就立刻進行信息反饋,而不用等待輪詢
                    #很高效,能夠在Linux上用
                    # selectors模塊,自動幫你選擇,你最適用的監聽機制,根本不須要關心

IO多路複用框架,例子及解釋:

import socket
import select

sk = socket.socket()
sk.bind(('127.0.0.1',8080))
sk.setblocking(False)
sk.listen()

read_lst = [sk]

while True:
    r_lst,w_lst,x_lst = select.select(read_lst,[],[]) #read_lst中的監聽對象,只有有迴應的,纔會出如今 r_lst
    print('***',r_lst) # 註釋1
    for i in r_lst:
        if i is sk:
            conn,addr = sk.accept()
            read_lst.append(conn)
        else:
            ret = i.recv(1024)
            if ret == b'':
                i.close()
                read_lst.remove(i)  # 此時 r_lst和read_lst是兩個不一樣內存空間,能夠remove
                continue
            print(ret)
            i.send(b'goodbye')

#註釋1
    #此時經歷過一次鏈接後,read_lst原本是有着 [conn,sk], 而後此時若是有人給我發消息,也就是我此時 conn.recv了
    #因爲 select模塊, 此時的r_lst只有 conn了,由於沒有人鏈接我,sk也就不會出如今 r_lst裏
server端
import time
import socket
import threading

def func():
    sk = socket.socket()
    sk.connect(('127.0.0.1',8080))
    sk.send(b'hello')
    time.sleep(3)
    print(sk.recv(1024))
    sk.close()

for i in range(20):
    threading.Thread(target=func).start()
client端

 

四.異步IO:

#異步IO
#步驟
    #1. 阻塞對象,recv,accept,告訴系統,我要數據,而後接着就去幹別的事了,不阻塞
    #2.操做系統就在那邊阻塞等待着數據,等到直到有數據傳來了
    #3.而後操做系統,直接將數據傳給用戶。
            # (!!)可是python在這一步,沒有提供這個copy data沒有提供python對操做系統的接口
            #因此不能用python代碼實現,真正的異步IO模型
            #可是c語言能夠,咱們可使用不少異步框架來實現
    #4.用戶收到數據了。


#django就不是異步框架
#異步框架:沒有waitdata和copydata的阻塞階段,能夠響應更多請求
    # twisted框架
    #tornado框架
    # tornado 和 twisted,做爲異步框架,是大同小異的。
    # 只不過tornado 輕量級一些,twisted 重量級一些。在其餘方面,也是互有長短。
    # 通過實測,發現這兩個框架,I/O性能差很少,對計算資源的佔用相差較多!
    # 若是追求總體性能的話,推薦使用twisted。
相關文章
相關標籤/搜索