1. 程式人生 > >python關於IO模型

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)幾種模型的比較
在這裡插入圖片描述