併發編程(三) IO模型

五 IO模型

經常使用的IO模型有4種:linux

  • 阻塞IO
  • 非阻塞IO
  • IO多路複用
  • 異步IO

不經常使用的有:windows

  • 驅動信號

5.1 阻塞IO、非阻塞IO

  • 阻塞IO:進程不能作其餘的事情
  • 非阻塞IO:等待數據無阻塞

阻塞IO

阻塞IO就是全程阻塞,其中,全程指的是等待數據和 數據從內核態拷貝到用戶態。網絡

全程阻塞就是以上兩個步驟都阻塞。如圖:併發

系統調用兩個階段:app

  • wait for data:阻塞
  • copy data:阻塞

非阻塞IO

非阻塞IO是部分阻塞,
等待數據時不會阻塞,而是在固定時間內循環發起系統調用,請求不到作本身的事情,等待下次請求,
而數據從內核態拷貝到用戶態仍是阻塞的。如圖:異步

系統調用兩個階段:socket

  • wait for data:非阻塞
  • copy data:阻塞

優勢:
等待數據無阻塞函數

缺點:
1.系統調用發送太多
2.數據不是即時接收的操作系統

ps:socket設置socket對象.setblocking(False) 設置阻塞狀態爲非阻塞code

5.2 IO多路複用

IO多路複用:全程阻塞,監聽多個連接

系統調用兩個階段:

  • wait for data:阻塞
  • copy data:阻塞

實現IO多路複用的經常使用方式有:

  • select
  • poll
  • epoll

原理

基本原理:
經過select/poll/epoll函數不斷輪詢所負責的全部socket套接字,當某個socket套接字有數據到達,就通知用戶進程。

特色:
就是單個process能夠同時處理多個網絡鏈接的IO,

ps:不一樣的操做系統提供的函數不一樣:
windows系統: select
linux系統: select、poll、epoll

select模塊

系統調用經過select模塊完成wait for data的工做

示例:
select監聽多個socket對象(sock是socket對象),實現併發

r, w, e = select.select([sock,], [], [])  # 等待連接 
for obj in r:
    conn, addr = obj.accept()

示例升級:

inputs = [sock,] 
r, w, e = select.select(inputs, [], [])  # inputs監聽有變化的套接字 inputs=[sock,conn1,conn2,...]
for obj in r:  # 第一次[sock,] 第二次[conn1,]
    if obj == sock:  # 若是返回的r = sock,說明有鏈接請求
        conn, addr = obj.accept()
        inputs.append(conn)  # inputs=[sock, conn1, conn2]
    else:  # 不然,能夠接收數據了
        data = obj.recv(1024)

ps:關於文件描述符的tips(socket套接字)

1.每個套接字對象的本質就是一個非零整數,不會變(fb=4)

<socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, 
proto=0, laddr=('127.0.0.1', 8080), raddr=('127.0.0.1', 51963)>

2.收發數據的時候,對於接收端而言,數據先到內核空間,而後copy到用戶空間,同時內核空間的數據被清空

3.根據TCP協議,當發送端接收到接收端的確認信息後,清空內核空間的數據,不然不清空

select、poll、epoll

select:

  • 每次調用select都要將全部的fd(文件描述符),copy到你的內核空間
  • 遍歷全部的fd,是否有數據訪問
  • 最大鏈接數(1024),超出連接再也不監聽

ps:select的特色也是其缺點,會致使效率降低:

poll:

  • 每次調用select都要將全部的fd(文件描述符),copy到你的內核空間
  • 遍歷全部的fd,是否有數據訪問
  • 最大鏈接數沒有限制

epoll:

  • 不一樣於select和poll只有一個函數,epoll經過三個函數實現實現輪詢socket:
    • 第一個函數:建立epoll句柄:將全部的fd(文件描述符),copy到你的內核空間,只copy一次
    • 回調函數:爲全部fd綁定一個回調函數,一旦有數據訪問,觸發回調函數,回調函數將fd放入一個鏈表中
    • 第三個函數:判斷鏈表是否爲空
  • epoll最大鏈接數沒有上線

ps:回調函數
某一個函數或者某一個動做,成功完成以後,會觸發的函數

selectors模塊

selectors是select的升級版

selectors基於select模塊實現IO多路複用,調用語句selectors.DefaultSelector()建立selecters對象,特色是根據平臺自動選擇最佳IO多路複用機制,調用順序:epoll > poll > select

import selectors
import socket

sel = selectors.DefaultSelector()  # 根據平臺自動選擇最佳IO多路複用機制

def accept(sock, mask):
    conn, addr = sock.accept()
    sel.register(conn, selectors.EVENT_READ, read)  # 將conn和read()註冊到一塊兒,當conn有變化時執行read()

def read(conn, mask):
    try:
        data = conn.recv(1000)
        print(data.decode('utf8'))
        inputs = input('>>:').strip()
        conn.send(inputs.encode('utf8'))
    except Exception:
        sel.unregister(conn)
        conn.close()

sock = socket.socket()
sock.bind(('127.0.0.1', 8080))
sock.listen(100)
sock.setblocking(False)  # 設置爲非阻塞IO

sel.register(sock, selectors.EVENT_READ, accept)  # 將sock和accept()註冊到一塊兒,當sock有變化時執行accept()

while True:
    events = sel.select()  # 監聽  [(key1,mask1),(key2),(mask2)]
    for key, mask in events:
        func = key.data  # 1 key.data就是accept   # 2 key.data就是read
        obj = key.fileobj  # 1 key.fileobj就是sock   # 2 key.fileobj就是conn

        func(obj, mask)  # 1 accept(sock,mask)   # 2read(conn,mask)

5.3 同步IO、異步IO

同步IO

只要系統調用中存在阻塞就是同步IO,
因此,阻塞IO、非阻塞IO、IO多路複用都是同步IO

異步IO

全程無阻塞,實現複雜

系統調用兩個階段:

  • wait for data:非阻塞
  • copy data:非阻塞
相關文章
相關標籤/搜索