【2020Python修煉記】python併發編程(八)IO模型

来源:https://www.cnblogs.com/bigorangecc/archive/2020/04/28/12795967.html
-Advertisement-
Play Games

【目錄】 一 IO模型介紹 二 阻塞IO(blocking IO) 三 非阻塞IO(non-blocking IO) 四 多路復用IO(IO multiplexing) 五 非同步IO(Asynchronous I/O) 六 IO模型比較分析 七 selectors模塊 本文討論的背景是Linux環境 ...


【目錄】

一 IO模型介紹

二 阻塞IO(blocking IO)

三 非阻塞IO(non-blocking IO)

四 多路復用IO(IO multiplexing)

五 非同步IO(Asynchronous I/O)

六 IO模型比較分析

七 selectors模塊

 

本文討論的背景是Linux環境下的network IO

一、 IO模型介紹

回顧:同步/非同步  阻塞/非阻塞

1、IO分類

同步(synchronous) IO

非同步(asynchronous) IO

阻塞(blocking) IO

非阻塞(non-blocking)IO

2、IO模型

五種IO Model:

* blocking IO、 * nonblocking IO、* IO multiplexing 、* signal driven IO  、* asynchronous IO

(由於signal driven IO(信號驅動IO)在實際中並不常用,所以主要介紹其餘四種 IO Model)

3、IO發生時涉及的對象和步驟

對於一個network IO (這裡我們以read舉例),它會涉及到兩個系統對象——

一個是 調用這個IO的process (or thread),另一個就是 系統內核(kernel)。

當一個read操作發生時,該操作會經歷兩個階段:

#1)等待數據準備 (Waiting for the data to be ready)

#2)將數據從內核拷貝到進程中(Copying the data from the kernel to the process)

記住這兩點很重要,因為這些IO模型的區別就是在兩個階段上各有不同的情況。

                   

 

4、補充

#1、輸入操作:read、readv、recv、recvfrom、recvmsg共5個函數,如果會阻塞狀態,則會經歷wait data和copy data兩個階段,如果設置為非阻塞則在wait 不到data時拋出異常 

#2、輸出操作:write、writev、send、sendto、sendmsg共5個函數,在發送緩衝區滿了會阻塞在原地,如果設置為非阻塞,則會拋出異常 

#3、接收外來鏈接:accept,與輸入操作類似 

#4、發起外出鏈接:connect,與輸出操作類似

 

二、 阻塞IO(blocking IO)

1、圖解

在linux中,預設情況下所有的socket都是blocking,一個典型的讀操作流程大概是這樣:

 

                                    

 

 

分析版——

                          

 

 

 

2、過程描述:

kernel(內核 / 操作系統):

當用戶進程調用了recvfrom這個系統調用,kernel就開始了IO的第一個階段:準備數據。

對於network io來說,很多時候數據在一開始還沒有到達(比如,還沒有收到一個完整的UDP包),這個時候kernel就要等待足夠的數據到來。 

用戶進程

而在用戶進程這邊,整個進程會被阻塞。當kernel一直等到數據準備好了,它就會將數據從kernel中拷貝到用戶記憶體,然後kernel返回結果,用戶進程才解除block的狀態,重新運行起來。

3、特點:

blocking IO的特點:在IO執行的兩個階段(等待數據和拷貝數據兩個階段)都被block了

4、補充:

所謂阻塞型介面是指系統調用(一般是IO介面)不返回調用結果並讓當前線程一直阻塞,只有當該系統調用獲得結果或者超時出錯時才返回。

實際上,除非特別指定,幾乎所有的IO介面 ( 包括socket介面 ) 都是阻塞型的。

如何解決調用阻塞問題——

在伺服器端使用多線程 / 多進程(讓每個連接都擁有獨立的線程(或進程),這樣任何一個連接的阻塞都不會影響其他的連接)

或者 使用 線程池 / 連接池(“線程池”旨在減少創建和銷毀線程的頻率,其維持一定合理數量的線程,並讓空閑的線程重新承擔新的執行任務。“連接池”維持連接的緩存池,儘量重用已有的連接、減少創建和關閉連接的頻率。這兩種技術都可以很好的降低系統開銷,都被廣泛應用很多大型系統,如websphere、tomcat和各種資料庫等。

上述兩種手段,都沒有很好地解決阻塞問題,該阻塞等待的還是如此——因此,嘗試使用 非阻塞IO 

"""
我們之前寫的都是阻塞IO模型  協程除外
"""
import socket


server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)


while True:
    conn, addr = server.accept()
    while True:
        try:
            data = conn.recv(1024)
            if len(data) == 0:break
            print(data)
            conn.send(data.upper())
        except ConnectionResetError as e:
            break
    conn.close()
    
# 在服務端開設多進程或者多線程 進程池線程池 其實還是沒有解決IO問題    
該等的地方還是得等 沒有規避
只不過多個人等待的彼此互不幹擾
典型的阻塞IO模型

 

三、 非阻塞IO(non-blocking IO)

1、圖解

Linux下,可以通過設置socket使其變為non-blocking。當對一個non-blocking socket執行讀操作時,流程是這個樣子:

 

                              

 

                         

 

 

 

 2、過程描述

# 當用戶進程發出read操作時,如果kernel中的數據還沒有準備好,那麼它並不會block用戶進程,而是立刻返回一個error。

從用戶進程角度講 ,它發起一個read操作後,並不需要等待,而是馬上就得到了一個結果。

用戶進程判斷結果是一個error時,它就知道數據還沒有準備好,於是用戶就可以在本次到下次再發起read詢問的時間間隔內做其他事情,或者直接再次發送read操作。

一旦kernel中的數據準備好了,並且又再次收到了用戶進程的system call,那麼它馬上就將數據拷貝到了用戶記憶體(這一階段仍然是阻塞的),然後返回。

#  也就是說非阻塞的recvform系統調用調用之後,進程並沒有被阻塞內核馬上返回給進程,如果數據還沒準備好,此時會返回一個error。進程在返回之後,可以乾點別的事情,然後再發起recvform系統調用。

重覆上面的過程,迴圈往複的進行recvform系統調用。這個過程通常被稱之為輪詢。輪詢檢查內核數據,直到數據準備好,再拷貝數據到進程,進行數據處理。

需要註意,拷貝數據整個過程,進程仍然是屬於阻塞的狀態

3、特點

在非阻塞式IO中,用戶進程其實是需要不斷的主動詢問kernel數據準備好了沒有。

非阻塞的recvform系統調用調用之後,進程並沒有被阻塞,內核馬上返回給進程,如果數據還沒準備好,此時會返回一個error。進程在返回之後,可以乾點別的事情,然後再發起recvform系統調用。

"""
要自己實現一個非阻塞IO模型
"""
import socket
import time


server = socket.socket()
server.bind(('127.0.0.1', 8081))
server.listen(5)
server.setblocking(False)
# 將所有的網路阻塞變為非阻塞
r_list = []
del_list = []
while True:
    try:
        conn, addr = server.accept()
        r_list.append(conn)
    except BlockingIOError:
        # time.sleep(0.1)
        # print('列表的長度:',len(r_list))
        # print('做其他事')
        for conn in r_list:
            try:
                data = conn.recv(1024)  # 沒有消息 報錯
                if len(data) == 0:  # 客戶端斷開鏈接
                    conn.close()  # 關閉conn
                    # 將無用的conn從r_list刪除
                    del_list.append(conn)
                    continue
                conn.send(data.upper())
            except BlockingIOError:
                continue
            except ConnectionResetError:
                conn.close()
                del_list.append(conn)
        # 揮手無用的鏈接
        for conn in del_list:
            r_list.remove(conn)
        del_list.clear()

# 客戶端
import socket


client = socket.socket()
client.connect(('127.0.0.1',8081))


while True:
    client.send(b'hello world')
    data = client.recv(1024)
    print(data)
需要自己實現一個非阻塞IO模型

4、補充——實際應用中,非阻塞IO模型 絕不被推薦。

優點:

能夠在等待任務完成的時間里乾其他活了(包括提交其他任務,也就是 “後臺” 可以有多個任務在“”同時“”執行)。

缺點:

#1. 迴圈調用recv()將大幅度推高CPU占用率;這也是我們在代碼中會設置 time.sleep( )的原因,

      否則在低配主機下極容易出現卡機情況

#2. 任務完成的響應延遲增大了,因為每過一段時間才去輪詢一次read操作,而任務可能在兩次輪詢之間的任意時間完成。

      這會導致整體數據吞吐量的降低。

 

四、 多路復用IO(IO multiplexing)——又名:事件驅動IO(event driven IO)

IO multiplexing,即 使用 select/epoll,其好處就在於單個process就可以同時處理多個網路連接的IO。

它的基本原理就是select/epoll 這個function會不斷的輪詢所負責的所有socket,當某個socket有數據到達了,就通知用戶進程。

它的流程如圖:

 

                                       

2、過程描述:

# 當用戶進程調用了select,那麼整個進程會被block,而同時,kernel會“監視”所有select負責的socket,

當任何一個socket中的數據準備好了,select就會返回。

這個時候用戶進程再調用read操作,將數據從kernel拷貝到用戶進程。

 

# 這個圖和blocking IO的圖其實並沒有太大的不同,事實上還更差一些。

因為這裡需要使用兩個系統調用(select和recvfrom),而blocking IO只調用了一個系統調用(recvfrom)。

但是,用select的優勢在於它可以同時處理多個connection。

 

3、強調--特點:

(1) 如果處理的連接數不是很高的話,使用select/epoll 的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延遲還更大。select/epoll的優勢並不是對於單個連接能處理得更快,而是在於能處理更多的連接。

(2)在多路復用模型中,對於每一個socket,一般都設置成為non-blocking,但是,如上圖所示,整個用戶的process其實是一直被block的。只不過process是被select這個函數block,而不是被socket IO給block。

結論: select的優勢在於可以處理多個連接,不適用於單個連接

"""
當監管的對象只有一個的時候 其實IO多路復用連阻塞IO都比不上!!!
但是IO多路復用可以一次性監管很多個對象

server = socket.socket()
conn,addr = server.accept()

監管機制是操作系統本身就有的 如果你想要用該監管機制(select)
需要你導入對應的select模塊
"""
import socket
import select


server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)
server.setblocking(False)
read_list = [server]


while True:
    r_list, w_list, x_list = select.select(read_list, [], [])
    """
    幫你監管
    一旦有人來了 立刻給你返回對應的監管對象
    """
    # print(res)  # ([<socket.socket fd=3, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080)>], [], [])
    # print(server)
    # print(r_list)
    for i in r_list:  #
        """針對不同的對象做不同的處理"""
        if i is server:
            conn, addr = i.accept()
            # 也應該添加到監管的隊列中
            read_list.append(conn)
        else:
            res = i.recv(1024)
            if len(res) == 0:
                i.close()
                # 將無效的監管對象 移除
                read_list.remove(i)
                continue
            print(res)
            i.send(b'heiheiheiheihei')

 # 客戶端
import socket


client = socket.socket()
client.connect(('127.0.0.1',8080))


while True:

    client.send(b'hello world')
    data = client.recv(1024)
    print(data)
多路復用代碼例子
"""
監管機制其實有很多——
# select機制  windows linux都有

# poll機制    只在linux有   

poll和select都可以監管多個對象 但是poll監管的數量更多 上述select和poll機制其實都不是很完美 當監管的對象特別多的時候 可能會出現 極其大的延時響應 # epoll機制 只在linux有 它給每一個監管對象都綁定一個回調機制 一旦有響應 回調機制立刻發起提醒 # 針對不同的操作系統還需要考慮不同檢測機制 書寫代碼太多繁瑣 有一個能夠根據你跑的平臺的不同自動幫你選擇對應的監管機制——selectors模塊
"""

 

五 、非同步IO(Asynchronous I/O)

Linux下的asynchronous IO其實用得不多,從內核2.6版本才開始引入。先看一下它的流程:

                                     

 

 

   

用戶進程發起read操作之後,立刻就可以開始去做其它的事。

而另一方面,從kernel的角度,當它受到一個asynchronous read之後,首先它會立刻返回,所以不會對用戶進程產生任何block。

然後,kernel會等待數據準備完成,然後將數據拷貝到用戶記憶體,

當這一切都完成之後,kernel會給用戶進程發送一個signal,告訴它read操作完成了

"""
非同步IO模型是所有模型中效率最高的 也是使用最廣泛的
相關的模塊和框架
    模塊:asyncio模塊
    非同步框架:sanic tronado twisted
        速度快!!!
"""
import threading
import asyncio


@asyncio.coroutine
def hello():
    print('hello world %s'%threading.current_thread())
    yield from asyncio.sleep(1)  # 換成真正的IO操作
    print('hello world %s' % threading.current_thread())


loop = asyncio.get_event_loop()
tasks = [hello(),hello()]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
非同步IO模型

 

六、 IO模型比較分析

1、blocking 和 non-blocking的區別

調用blocking IO會一直block住對應的進程直到操作完成,而non-blocking IO在kernel還在準備數據的情況下會立刻返回。

2、synchronous IO (同步)和 asynchronous IO(非同步)的區別

兩者的定義:

 A synchronous I/O operation causes the requesting process to be blocked until that I/O operationcompletes;

An asynchronous I/O operation does not cause the requesting process to be blocked

兩者的區別就在於synchronous IO做”IO operation”的時候會將process阻塞。

按照這個定義,四個IO模型可以分為兩大類,

之前所述的 blocking IO,non-blocking IO,IO multiplexing都屬於 synchronous IO這一類,

而 asynchronous IO 為另一類 。

各個IO Model的比較如圖所示:

                        

 

 

 

七、 selectors模塊(暫略)

 

 

參考資料:

https://zhuanlan.zhihu.com/p/112185254

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一丶簡介 Fanout Exchange 不處理路由鍵。你只需要簡單的將隊列綁定到交換機上。一個發送到交換機的消息都會被轉發到與該交換機綁定的所有隊列上。很像子網廣播,每檯子網內的主機都獲得了一份複製的消息。Fanout交換機轉發消息是最快的。 業務場景: 1.訂單服務需要同時向簡訊服務和push服 ...
  • 1. spring boot lll starter自動化框架介紹 1.1. 前言 舔著臉來介紹一波我剛寫的自動化框架, "spring boot lll starter" 框架是經由我企業實戰總結的一套,適用於項目起始構建的框架,適配了管理後臺和微服務項目兩種方案的代碼生成 我做了一個簡短的dem ...
  • C語言被忽視的一些小東西!C語言基礎教程之錯誤處理。 C 語言不提供對錯誤處理的直接支持,但是作為一種系統編程語言,它以返回值的形式允許您訪問底層數據。在發生錯誤時,大多數的 C 或 UNIX 函數調用返回 1 或 NULL,同時會設置一個錯誤代碼errno,該錯誤代碼是全局變數,表示在函數調用期間 ...
  • 任何可以產生對象的方法或者類,都可以稱之為工廠。單例就是所謂的靜態工廠。 為什麼jdk中有了new,還需要工廠呢? a、靈活的控制生產過程 b、給對象加修飾、或者給對象加訪問許可權,或者能夠在對象生產過程中添加一些日誌信息,再或者根據應用場景添加一些實際的業務處理等等。 1、靜態工廠 單例模式:一種特 ...
  • 一、JSR 303 1、什麼是 JSR 303? JSR 是 Java Specification Requests 的縮寫,即 Java 規範提案。 存在各種各樣的 JSR,簡單的理解為 JSR 是一種 Java 標準。 JSR 303 就是數據檢驗的一個標準(Bean Validation (J ...
  • 概述 在使用Python或者其他的編程語言,都會多多少少遇到編碼錯誤,處理起來非常痛苦。在Stack Overflow和其他的編程問答網站上,UnicodeDecodeError和UnicodeEncodeError也經常被提及。本篇教程希望能幫你認識Python編碼,並能夠從容的處理編碼問題。 這 ...
  • 一丶簡介 Topic Exchange 將路由鍵和某模式進行匹配。此時隊列需要綁定要一個模式上。符號“#”匹配一個或多個詞,符號“*”匹配不多不少一個詞。因此“audit.#”能夠匹配到“audit.irs.corporate”,但是“audit.*” 只會匹配到“audit.irs”。 業務場景: ...
  • 場景 我們用Django的Model時,有時候需要關聯外鍵。關聯外鍵時,參數: 的幾個配置選項到底是幹嘛的呢,你知道嗎? 參數介紹 級聯刪除。Django會模擬SQL約束的行為,在刪除此條數據時,同事刪除外鍵關聯的對象。 比如:用戶的有一個外鍵關聯的是用戶的健康記錄表,當用戶刪除時,配置了這個參數的 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...