IO模型《三》非阻塞IO
阿新 • • 發佈:2018-12-11
非阻塞IO(non-blocking IO)
Linux下,可以通過設定socket使其變為non-blocking。當對一個non-blocking socket執行讀操作時,流程是這個樣子:
從圖中可以看出,當用戶程序發出read操作時,如果kernel中的資料還沒有準備好,那麼它並不會block使用者程序,而是立刻返回一個error。從使用者程序角度講 ,它發起一個read操作後,並不需要等待,而是馬上就得到了一個結果。使用者程序判斷結果是一個error時,它就知道資料還沒有準備好,於是使用者就可以在本次到下次再發起read詢問的時間間隔內做其他事情,或者直接再次傳送read操作。一旦kernel中的資料準備好了,並且又再次收到了使用者程序的system call,那麼它馬上就將資料拷貝到了使用者記憶體(這一階段仍然是阻塞的),然後返回。
也就是說非阻塞的recvform系統呼叫呼叫之後,程序並沒有被阻塞,核心馬上返回給程序,如果資料還沒準備好,
此時會返回一個error。程序在返回之後,可以乾點別的事情,然後再發起recvform系統呼叫。重複上面的過程,
迴圈往復的進行recvform系統呼叫。這個過程通常被稱之為輪詢。輪詢檢查核心資料,直到資料準備好,再拷貝資料到程序,
進行資料處理。需要注意,拷貝資料整個過程,程序仍然是屬於阻塞的狀態。
所以,在非阻塞式IO中,使用者程序其實是需要不斷的主動詢問kernel資料準備好了沒有。
非阻塞IO示例
#服務端 from socket import * server = socket(AF_INET, SOCK_STREAM) server.bind(('127.0.0.1',8099)) server.listen(5) server.setblocking(False) rlist=[] wlist=[] while True: try: conn, addr = server.accept() rlist.append(conn) print(rlist) except BlockingIOError: del_rlist=[] for sock in rlist: try: data=sock.recv(1024) if not data: del_rlist.append(sock) wlist.append((sock,data.upper())) except BlockingIOError: continue except Exception: sock.close() del_rlist.append(sock) del_wlist=[] for item in wlist: try: sock = item[0] data = item[1] sock.send(data) del_wlist.append(item) except BlockingIOError: pass for item in del_wlist: wlist.remove(item) for sock in del_rlist: rlist.remove(sock) server.close() #客戶端 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模型絕不被推薦。
我們不能否則其優點:能夠在等待任務完成的時間裡幹其他活了(包括提交其他任務,也就是 “後臺” 可以有多個任務在“”同時“”執行)。
但是也難掩其缺點:
1. 迴圈呼叫recv()將大幅度推高CPU佔用率;這也是我們在程式碼中留一句time.sleep(2)的原因,否則在低配主機下極容易出現卡機情況
2. 任務完成的響應延遲增大了,因為每過一段時間才去輪詢一次read操作,而任務可能在兩次輪詢之間的任意時間完成。
這會導致整體資料吞吐量的降低。
此外,在這個方案中recv()更多的是起到檢測“操作是否完成”的作用,實際作業系統提供了更為高效的檢測“操作是否完成“作用的介面,例如select()多路複用模式,可以一次檢測多個連線是否活躍。