io多路復用概念
io發生時涉及的物件和步驟。對於乙個網路io,它會涉及到兩個系統物件,乙個是呼叫io的程序或者執行緒,另乙個就是系統核心。
如當乙個read操作發生時,會先等待資料準備,然後將資料從核心拷貝到程序中去
阻塞io(blocking io)特點:在執行io的兩個階段(等待資料和拷貝資料兩個階段)都被阻塞了
實際上幾乎所有的io藉口(包括socket埠)都是阻塞型的。如在呼叫recv(1024)的同時,執行緒將被阻塞,在此期間,執行緒將無法執行任何運算或響應任何的網路請求。
解決方案:在服務端使用多執行緒(或者多程序),目的是讓每個連線都擁有獨立的執行緒(或者程序)。但是一旦客戶端同一時間的連線請求過多的話,則會嚴重占用系統資源,降低系統對外界的響應效率。
那麼該如何解決呢?最好的辦法就是使用『執行緒池』或者『連線池』。「執行緒池」旨在減少建立和銷毀執行緒的頻率,其維持一定合理數量的執行緒,並讓空閒的執行緒重新承擔新的執行任務。「連線池」維持連線的快取池,盡量重用已有的連線、減少建立和關閉連線的頻率。這兩種辦法可以很好地降低系統開銷,適用於大型系統。
上述方案只是一定程度緩解了頻繁呼叫io介面帶來的資源占用,『池』終究是有上限的,當響應規模過大時,必須考慮調整池的大小。
用解決該問題。可以使用非阻塞介面來解決
多執行緒,多程序,程序池,執行緒池都可以實現併發,但是仍然沒有解決io問題,先看一下io非阻塞模型(linux下)
從圖中可以看出,當使用者程序發出read操作時,如果kernel中的資料還沒有準備好,那麼它並不會block使用者程序,而是立刻返回乙個error。從使用者程序角度講 ,它發起乙個read操作後,並不需要等待,而是馬上就得到了乙個結果。使用者程序判斷結果是乙個error時,它就知道資料還沒有準備好,於是使用者就可以在本次到下次再發起read詢問的時間間隔內做其他事情,或者直接再次傳送read操作。一旦kernel中的資料準備好了,並且又再次收到了使用者程序的system call,那麼它馬上就將資料拷貝到了使用者記憶體(這一階段仍然是阻塞的),然後返回。
也就是說非阻塞的recvform系統呼叫呼叫之後,程序並沒有被阻塞,核心馬上返回給程序,如果資料還沒準備好,此時會返回乙個error。程序在返回之後,可以幹點別的事情,然後再發起recvform系統呼叫。重複上面的過程,迴圈往復的進行recvform系統呼叫。這個過程通常被稱之為輪詢。輪詢檢查核心資料,直到資料準備好,再拷貝資料到程序,進行資料處理。需要注意,拷貝資料整個過程,程序仍然是屬於阻塞的狀態。
所以,在非阻塞式io中,使用者程序其實是需要不斷的主動詢問kernel資料準備好了沒有。
相關引數
server.setblocking() #server端預設是true
server.setblocking(false)
#false的話就成非阻塞了,這只是對於socket套接字來說的
#所以,在非阻塞式io中,使用者程序其實是需要不斷的主動詢問核心資料準備好了沒有。
wait data
#等資料,這個階段是不阻塞的
copy data
#這個階段還是要阻塞的
import socketclient端sk = socket.socket()
sk.bind(('127.0.0.1',9000))
sk.setblocking(false)
sk.listen()
conn_lst =
del_lst =
while true:
try:
conn,addr = sk.accept() # 非阻塞的模型
print(conn,addr)
except blockingioerror as e:
for conn in conn_lst: #[conn1,conn2,conn3]
try:
msg= conn.recv(1024) # 非阻塞
if not msg: # 判定是否為空
conn.close()
continue
print(msg)
msg = msg.decode('utf-8').upper()
conn.send(msg.encode('utf-8'))
except blockingioerror:pass # 阻塞io報錯,直接pass
for conn in del_lst:
conn_lst.remove(conn)
del_lst.clear()
import time非阻塞io模型優點:import socket
sk = socket.socket()
sk.connect(('127.0.0.1',9000))
print(sk)
for i in range(20):
sk.send(b'hello')
print(sk.recv(1024))
time.sleep(1)
sk.close()
能夠在等待任務完成的時間裡幹其他活了(包括提交其他任務,也就是 「後台」 可以有多個任務在「」同時「」執行)。
非阻塞io模型缺點:
1. 迴圈呼叫recv()將大幅度推高cpu佔用率
2. 任務完成的響應延遲增大了,因為每過一段時間才去輪詢一次read操作,而任務可能在兩次輪詢之間的任意時間完成。這會導致整體資料吞吐量的降低。
當使用者程序呼叫了select,那麼整個程序會被block,而同時,kernel會「監視」所有select負責的socket,當任何乙個socket中的資料準備好了,select就會返回。這個時候使用者程序再呼叫read操作,將資料從kernel拷貝到使用者程序。
作業系統中的io多路復用的機制select:
windows作業系統提供給你的一種監聽接收資料io的乙個**
select模組:
python使用作業系統select機制的功能
server端:
import socketclient端:import select
sk = socket.socket()
sk.bind(('127.0.0.1',9000))
sk.setblocking(false)
sk.listen()
l = [sk] # sk.accept
while true:
r,w,x = select.select(l,,) # 阻塞
for obj in r:
if obj is sk:
conn,addr = obj.accept()
else:
msg = obj.recv(1024)
if not msg:
obj.close()
l.remove(obj)
continue
print(msg)
obj.send(b'bye')
import socket該模型優點:sk = socket.socket()
sk.connect(('127.0.0.1',9000))
while true:
sk.send(b'hello')
print(sk.recv(1024))
sk.close()
相比其他模型,使用select() 的事件驅動模型只用單執行緒(程序)執行,占用資源少,不消耗太多 cpu,同時能夠為多客戶端提供服務。如果試圖建立乙個簡單的事件驅動的伺服器程式,這個模型有一定的參考價值。
該模型缺點:
首先select()介面並不是實現「事件驅動」的最好選擇。因為當需要探測的控制代碼值較大時,select()介面本身需要消耗大量時間去輪詢各個控制代碼。
對於io多路復用(select)
select檢測的是哪個套接字準備好了(檢測的時候等待了,變成阻塞了)
select之所以比阻塞io好,就是因為select可以檢測多個套接字
多個鏈結下select才能發揮它的優勢
但是你的套接字特別多,你怎麼知道哪個好了呢,那麼就得用迴圈去遍歷一下
那麼如果特別多的時候,效率也就不咋高了
epoll:只支援linux系統(就是為了解決select效率低的問題)
epoll比pool,select效率高
selectors 更好用,解決了上面select,epoll,pool的問題
socketserver用這個模組io問題也解決了,實現併發也解決了(見鏈結文章最底部例項
)
網路程式設計之 IO模型
我們這裡研究的io模型都是針對網路io的 blocking io 阻塞io nonblocking io 非阻塞io io multiplexing io多路復用 signal driven io 訊號驅動io asynchronous io 非同步io 由signal driven io 訊號驅動...
網路程式設計之IO模型 非同步IO
linux下的asynchronous io其實用得不多,從核心2.6版本才開始引入。先看一下它的流程 使用者程序發起read操作之後,立刻就可以開始去做其它的事。而另一方面,從kernel的角度,當它受到乙個asynchronous read之後,首先它會立刻返回,所以不會對使用者程序產生任何bl...
Linux網路程式設計之IO模型
同步是指乙個任務的完成需要依賴另外乙個任務時,只有等待被依賴的任務完成後,依賴的任務才能算完成。非同步是指不需要等待被依賴的任務完成,只是通知被依賴的任務要完成什麼工作,依賴的任務也立即執行,只要自己完成了整個任務就算完成了,非同步一般使用狀態 通知和 阻塞是指呼叫結果返回之前,當前執行緒會被掛起,...