併發編程-IO模型

IO模型

模型即解決某個問題的固定套路html

I/O 指的是輸入輸出python

IO的問題: 當咱們要輸入數據或是輸出數據一般須要很長一段時間,固然是對於CPU而言linux

在等待輸入的過程當中,CPU就處於閒置狀態 沒事幹! 形成了資源浪費服務器

注意: IO其實有不少類型,例如,socket網絡IO,內存到內存的copy,等待鍵盤輸入,對比起來socket網絡IO須要等待的時間是最長的,這也是我們重點關注的地方,網絡

學習IO模型要幹什麼? 就是在等待IO操做的過程當中利用CPU,作別的事情多線程

網絡IO經歷的步驟和過程

操做系統有兩種狀態:內核態 和 用戶態 , 當操做系統須要控制硬件時,例如接收網卡上的數據,必須先轉換到內核態,接收完數據後,要把數據從操做系統緩衝區,copy到應用程序的緩衝區,從內核態轉爲用戶態;併發

涉及到的步驟app

1.wait_data異步

2.copy_datasocket

recv accept 須要經歷 wait -> copy

send 只須要經歷copy

阻塞IO模型

默認狀況下 你寫出TCP程序就是阻塞IO模型

該模型 提升效率方式,當你執行recv/accept 會進入wait_data的階段,

1.你的進程會主動調用一個block指令,進程進入阻塞狀態,同時讓出CPU的執行權,操做系統就會將CPU分配給其它的任務,從而提升了CPU的利用率

2.當數據到達時,首先會從內核將數據copy到應用程序緩衝區,而且socket將喚醒處於自身的等待隊列中的全部進程

注意:以前使用多線程 多進程 完成的併發 其實都是阻塞IO模型 每一個線程在執行recv時,也會卡住

img

非阻塞IO模型

非阻塞IO模型與阻塞模型相反 ,在調用recv/accept 時都不會阻塞當前線程

使用方法: 將本來阻塞的socket 設置爲非阻塞

img

該模型在沒有數據到達時,會拋出異常,咱們須要捕獲異常,而後繼續不斷地詢問系統內核直到數據到達爲止
案例:

import socket
import time
s = socket.socket()
s.bind(("127.0.0.1",1688))
# 設置爲非阻塞 模型
s.setblocking(False) #False表示不阻塞
s.listen()

# 保存全部的客戶端socket
cs = []

# 用於保存須要發送的數據
msgs = []

while True:
    try:
        c,addr = s.accept()  # 完成三次握手
        print("來客了, xxx接客了!")
        cs.append(c)
    except BlockingIOError:
        print("尚未人來/. 好寂寞啊!")
        #  代碼執行到這裏則說明 沒有鏈接須要處理
        #  就能夠處理收發數據的任務
        for c in cs[:]:  # 專門處理接收數據
            try:
                data = c.recv(1024)
                if not data:
                   raise ConnectionResetError
                msgs.append((c,data.upper()))
                #c.send(data.upper()) # 不能直接發  當內核緩衝區滿了 就會拋出異常 致使數據發不出去了
            except BlockingIOError:
                print("客官 你卻是說句話啊!")
                pass
            except ConnectionResetError:
                c.close()
                cs.remove(c)

        # 處理髮送數據
        for i in msgs[:]:
            try:
                i[0].send(i[1])
                msgs.remove(i)
            except BlockingIOError:
                pass
            except ConnectionResetError:
                #關閉鏈接
                i[0].close()
                # 刪除數據
                msgs.remove(i)
                # 刪除鏈接
                cs.remove(i[0])

客戶端

import socket
c = socket.socket()
c.connect(("127.0.0.1",1688))

while True:
    msg = input("").strip()
    if not msg:continue
    c.send(msg.encode("utf-8"))
    print(c.recv(1024).decode('utf-8'))

能夠看出,該模型會大量的佔用CPU資源作一些無效的循環, 效率低於阻塞IO

多路複用IO模型

屬於事件驅動模型

多個socket使用同一套處理邏輯

若是將非阻塞IO 比喻是點餐的話,至關於你每次去前臺,照着菜單挨個問個遍

而多路複用,至關於直接問前臺那些菜作好了,前臺會給你返回一個列表,裏面就是已經作好的菜

對比阻塞或非阻塞模型,增長了一個select,來幫咱們檢測socket的狀態,從而避免了咱們本身檢測socket帶來的開銷

select會把已經就緒的socket放入列表中,咱們須要遍歷列表,分別處理讀寫便可

img

多路複用中select的職責:

案例:

import socket
import time
import select
s = socket.socket()
s.bind(("127.0.0.1",1688))
# 設置爲非阻塞 模型
s.setblocking(True) #在多路複用中  阻塞與非阻塞沒有區別 由於select會阻塞直到有數據到達爲止
s.listen()

# 待檢測是否可讀的列表
r_list = [s]
# 待檢測是否可寫的列表
w_list = []

# 待發送的數據
msgs = {}

print("開始檢測了")
while True:
    read_ables, write_ables, _= select.select(r_list,w_list,[])
    print("檢測出結果了!")
    # print(read_ables,"能夠收數據了")
    # print(write_ables,"能夠發數據了")
    # 處理可讀 也就是接收數據的
    for obj in read_ables: # 拿出全部能夠讀數據的socket
        #有多是服務器 有多是客戶端
        if s == obj: # 服務器
            print("來了一個客戶端 要鏈接")
            client,addr = s.accept()
            r_list.append(client)  # 新的客戶端也交給select檢測了
            
        else:# 若是是客戶端則執行recv 接收數據
            print("客戶端發來一個數據")
            try:
                data = obj.recv(1024)
                if not data:raise ConnectionResetError
                print("有個客戶端說:",data)
                # 將要發送數據的socket加入到列表中讓select檢測
                w_list.append(obj)
                # 將要發送的數據已經socket對象丟到容器中
                if obj in msgs:  # 因爲容器是一個列表 因此須要先判斷是否已經存在了列表
                    msgs[obj].append(data)
                else:
                    msgs[obj] = [data]
            except ConnectionResetError:
                obj.close()
                r_list.remove(obj)
    # 處理可寫的 也就是send發送數據
    for obj in write_ables:
        msg_list = msgs.get(obj)
        if msg_list:
            # 遍歷發送全部數據
            for m in msg_list:
                try:
                    obj.send(m.upper())
                except ConnectionResetError:
                    obj.close()
                    w_list.remove(obj)
                    break
            # 數據從容器中刪除
            msgs.pop(obj)
        # 將這個socket從w_list中刪除
        w_list.remove(obj)

客戶端

import socket
c = socket.socket()
c.connect(("127.0.0.1",1688))

while True:
    msg = input("").strip()
    if not msg:continue
    c.send(msg.encode("utf-8"))
    print(c.recv(1024).decode('utf-8'))

多路複用對比非阻塞 ,多路複用能夠極大下降CPU的佔用率

注意:多路複用並不完美 由於本質上多個任務之間是串行的,若是某個任務耗時較長將致使其餘的任務不能當即執行,多路複用最大的優點就是高併發

另外select 最大僅支持1024的socket同時處理,因此linux下的epoll纔是最好的多路複用模型,詳見:

http://www.javashuo.com/article/p-wfbzikdm-dc.html

異步IO模型

非阻塞IO不等於異步IO 由於copy的過程是一個同步任務 會卡主當前線程

而異步IO 是發起任務後 就能夠繼續執行其它任務,當數據copy到應用程序緩衝區完成後,纔會給你的線程發送信號 或者執行回調

asyncio python3.4 出現 目前不少重要的第三方模塊都不支持該模塊,因此沒有普遍使用,其內部也是使用了生成器!

信號驅動IO模型

簡單的說就是 當某個事情發生後 會給你的線程發送一個信號,你的線程就能夠去處理這個任務

不經常使用,緣由是 socket的信號太多,處理起來很是繁瑣

相關文章
相關標籤/搜索