python關於IO模型
1.事件驅動程式設計
(1)定義:傳統的計算機程式執行都是線性的, 也即程式的執行順序,行為都是可預測的, 而事件驅動的程式的執行與外部事件有關, 外部輸入什麼操作,程式就執行相應的函式,完全由事件驅動,事前不可預測;
(2)執行流程:事件驅動程式的一個基本結構是一個事件收集器, 一個事件傳送器, 一個事件處理器;
1.有一個事件佇列, 其中每個事件對應一個動作;
2.使用者發生一個操作, 就往事件佇列中加入一個事件;
3.有一個迴圈, 不斷從佇列中獲取事件, 然後根據不同事件分配給不同函式;
4.事件一般會儲存各自處理函式的指標
注意:當佇列中沒有事件時, 會一直等待(阻塞), 可選擇性的釋放cpu.
事件驅動的監聽事件是由作業系統呼叫的cpu來完成的
事件驅動的情況下實現IO自動切換, 即是下面說到的IO多路複用.
2.幾種IO模型
(1)阻塞IO(blocking IO)
預設的socket程式設計即使阻塞型的, 特點是等待資料和拷貝資料到使用者空間 的階段都是阻塞的. 下圖為讀操作的流程圖:
使用者程式呼叫recvfrom, recvfrom向核心發出系統呼叫, 然後核心直到資料拷貝到使用者記憶體後才返回結果給使用者, 使用者程序便可直接調取,整個過程一直阻塞
缺點: 程式一直阻塞, 無法執行後續操作;
這樣要想實現併發, 可以使用多執行緒, 多程序, 或執行緒池等方式.
而多執行緒一旦連線過多,執行緒過多,會極大消耗系統資源;
(2)非阻塞IO(non-blocking IO)
recvfrom發起系統呼叫後, 如果資料未準備好, 核心也會立即返回, 這樣使用者程式可以不用阻塞繼續執行後面的操作, 但需要迴圈向核心發出系統呼叫來查詢資料是否準備好, 一旦資料準備好,recvfrom便會發起系統呼叫從核心空間拷貝資料,拷貝資料的過程是阻塞的;
缺點:大量進行系統呼叫, 會極大消耗cpu資源; 同時由於查詢間隔, 將不能及時的獲取資料;
可通過迴圈呼叫recv的方式實現多連線,一般很少用這種方案;
示例:
import socket
import time
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s. bind(('127.0.0.1', 8000))
s.listen(5)
s.setblocking(False) # 非阻塞套接字
while True:
try:
con, addr = s.accept()
while True:
try:
data = con.recv(1024)
print(data.decode('utf8'))
msg = input('傳送:').encode('utf8')
con.send(msg)
except BlockingIOError as e1:
time.sleep(2)
continue
except ConnectionResetError:
break
except BlockingIOError as e:
time.sleep(2) # 無限輪詢
#client
import socket
c = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
c.connect(('127.0.0.1', 8000))
while True:
msg = input('傳送:')
c.send(msg.encode('utf8'))
data = c.recv(1024)
print(data.decode('utf8'))
(3)多路複用IO(multiplexing IO)
這種方式通過select(poll,epoll)發出系統呼叫, 如果資料未準備好,程式會一直阻塞,直到資料準備好,核心返回結果, 然後recv就可發出系統呼叫,從核心空間拷貝資料, 此過程也是阻塞的
優點:雖然與阻塞IO相比還多一次系統呼叫, 但select(epoll, poll)可實現同時對多個連線的監聽,在單執行緒實現併發;
缺點:一旦事件相應的執行函式太龐大, 將會嚴重影響select的監聽, 下一個事件的響應遲遲得不到執行;(解決方案:高效的事件驅動庫可以遮蔽上述的困難,常見的事件驅動庫有libevent庫,還有作為libevent替代者的libev庫)
epoll的優點: select最多隻能監聽1024個物件, 而epoll幾乎沒有限制;另外select探測事件是去輪詢每個檔案描述符, 比較耗時, 而epoll是隻取出有變化的事件; 另外select需要在使用者記憶體和核心記憶體間傳遞檔案描述符陣列, 會消耗記憶體空間;poll相比select則避開了最多監聽物件的限制;
例項1: select 多路複用實現併發
import socket
import select
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 8000))
s.listen(5)
s.setblocking(False)
r_list = [s]
while True:
r, w, e = select.select(r_list, [], [])
for i in r:
if i == s:
con, addr = i.accept()
print(addr, '連線成功')
r_list.append(con)
else:
data = i.recv(1024)
print(data.decode('utf8'))
msg = input('傳送:')
i.send(msg.encode('utf8'))
#client
import socket
c = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
c.connect(('127.0.0.1', 8000))
while True:
msg = input('傳送:')
c.send(msg.encode('utf8'))
data = c.recv(1024)
print(data.decode('utf8'))
(4)非同步IO(asynchrounous IO)
非同步IO整個過程沒有阻塞, aio_read發出系統呼叫後核心立即返回, 不影響程式的執行, 然後直到核心將資料拷貝到使用者記憶體,才通知使用者程序
(5)訊號驅動IO(signal driven IO)
(6)幾種模型的比較