Python 的五種io模型理解


一 IO操作本質

數據復制的過程中不會消耗CPU

1
2
3
# 1 內存分為內核緩沖區和用戶緩沖區
# 2 用戶的應用程序不能直接操作內核緩沖區,需要將數據從內核拷貝到用戶才能使用
# 3 而IO操作、網絡請求加載到內存的數據一開始是放在內核緩沖區的

image-20200325231658991

二 IO模型

1. BIO – 阻塞模式I/O

用戶進程從發起請求,到最終拿到數據前,一直掛起等待; 數據會由用戶進程完成拷貝

1
2
3
4
5
6
7
8
9
10
11
12
13
'''
舉個例子:一個人去 商店買一把菜刀,
他到商店問老板有沒有菜刀(發起系統調用)
如果有(表示在內核緩沖區有需要的數據)
老板直接把菜刀給買家(從內核緩沖區拷貝到用戶緩沖區)
這個過程買家一直在等待

如果沒有,商店老板會向工廠下訂單(IO操作,等待數據准備好)
工廠把菜刀運給老板(進入到內核緩沖區)
老板把菜刀給買家(從內核緩沖區拷貝到用戶緩沖區)
這個過程買家一直在等待
是同步io
'''

image-20200325231903075

2. NIO – 非阻塞模式I/O

用戶進程發起請求,如果數據沒有准備好,那么立刻告知用戶進程未准備好;此時用戶進程可選擇繼續發起請求、或者先去做其他事情,稍后再回來繼續發請求,直到被告知數據准備完畢,可以開始接收為止; 數據會由用戶進程完成拷貝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
'''
舉個例子:一個人去 商店買一把菜刀,
他到商店問老板有沒有菜刀(發起系統調用)
老板說沒有,在向工廠進貨(返回狀態)
買家去別地方玩了會,又回來問,菜刀到了么(發起系統調用)
老板說還沒有(返回狀態)
買家又去玩了會(不斷輪詢)
最后一次再問,菜刀有了(數據准備好了)
老板把菜刀遞給買家(從內核緩沖區拷貝到用戶緩沖區)

整個過程輪詢+等待:輪詢時沒有等待,可以做其他事,從內核緩沖區拷貝到用戶緩沖區需要等待
是同步io

同一個線程,同一時刻只能監聽一個socket,造成浪費,引入io多路復用,同時監聽讀個socket
'''

image-20200325232410816

3. IO Multiplexing - I/O多路復用模型

類似BIO,只不過找了一個代理,來掛起等待,並能同時監聽多個請求; 數據會由用戶進程完成拷貝

1
2
3
4
5
6
7
8
9
10
11
12
13
'''
舉個例子:多個人去 一個商店買菜刀,
多個人給老板打電話,說我要買菜刀(發起系統調用)
老板把每個人都記錄下來(放到select中)
老板去工廠進貨(IO操作)
有貨了,再挨個通知買到的人,來取刀(通知/返回可讀條件)
買家來到商店等待,老板把到給買家(從內核緩沖區拷貝到用戶緩沖區)

多路復用:老板可以同時接受很多請求(select模型最大1024個,epoll模型),
但是老板把到給買家這個過程,還需要等待,
是同步io

'''

image-20200325232430850

4. AIO – 異步I/O模型

發起請求立刻得到回復,不用掛起等待; 數據會由內核進程主動完成拷貝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
'''
舉個例子:還是買菜刀
現在是網上下單到商店(系統調用)
商店確認(返回)
商店去進貨(io操作)
商店收到貨把貨發個賣家(從內核緩沖區拷貝到用戶緩沖區)
買家收到貨(指定信號)

整個過程無等待
異步io

AIO框架在windows下使用windows IOCP技術,在Linux下使用epoll多路復用IO技術模擬異步IO

市面上多數的高並發框架,都沒有使用異步io而是用的io多路復用,因為io多路復用技術很成熟且穩定,並且在實際的使用過程中,異步io並沒有比io多路復用性能提升很多,沒有達到很明顯的程度
並且,真正的AIO編碼難度比io多路復用高很多
'''

image-20200325232454769

5 select poll 和epoll

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 1 select poll 和epoll都是io多路復用技術
select, poll , epoN都是io多路復用的機制。I/O多路復用就是通過一種機 制個進程可以監視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程序進行相應的讀寫操作。但select, poll , epoll本質上都是同步I/O ,因為他們都需要在讀寫事件就緒后自己負責進行讀寫, 也就是說這個讀寫過程是阻塞的,而異步I/O則無需自己負責進行讀寫,異 步I/O的實現會負責把數據從內核拷貝到用戶空間。

# 2 select
select函數監視的文件描述符分3類,分別是writefds、readfds、和 exceptfds。調用后select函數會阻塞,直到有描述副就緒(有數據可讀、 可寫、或者有except),或者超時(timeout指定等待時間,如果立即返回 設為null即可),函數返回。當select函數返回后,可以通過遍歷fdset,來 找到就緒的描述符。
select目前幾乎在所有的平台上支持,其良好跨平台支持也是它的一個 優點。select的一個缺點在於單個進程能夠監視的文件描述符的數量存在最大限制,在Linux上一般為1024 ,可以通過修改宏定義甚至重新編譯內核的 方式提升這一限制,但是這樣也會造成效率的降低。
# 3 poll
不同於select使用三個位圖來表示三個fdset的方式,poll使用一個 pollfd的指針實現。
pollfd結構包含了要監視的event和發生的event,不再使用select '參數-值'傳遞的方式。同時,pollfd並沒有最大數量限制(但是數量過大后 性能也是會下降)。和select函數一樣,poll返回后,需要輪詢pollfd來獲取就緒的描述符。
從上面看,select和poll都需要在返回后,通過遍歷文件描述符來獲取 已經就緒的socket。事實上,同時連接的大量客戶端在一時刻可能只有很少的處於就緒狀態,因此隨着監視的描述符數量的增長,其效率也會線性下降

# 4 epoll
epoll是在linux2.6內核中提出的,是之前的select和poll的增強版本。相對 於select和poll來說,epoll更加靈活,沒有描述符限制。epoll使用一個文 件描述符管理多個描述符,將用戶關系的文件描述符的事件存放到內核的一個事件表中,這樣在用戶空間和內核空間的copy只需一次。

# 5 更好的例子理解
老師檢查同學作業,一班50個人,一個一個問,同學,作業寫完了沒?select,poll
老師檢查同學作業,一班50個人,同學寫完了主動舉手告訴老師,老師去檢查 epoll

# 6 總結
在並發高的情況下,連接活躍度不高,epoll比select好,網站http的請求,連了就斷掉
並發性不高,同時連接很活躍,select比epoll好,websocket的連接,長連接,游戲開發

三 同步I/O與異步I/O

  • 同步I/O
    • 概念:導致請求進程阻塞的I/O操作,直到I/O操作任務完成
    • 類型:BIO、NIO、IO Multiplexing
  • 異步I/O
    • 概念:不導致進程阻塞的I/O操作
    • 類型:AIO

注意:

  • 同步I/O與異步I/O判斷依據是,是否會導致用戶進程阻塞
  • BIO中socket直接阻塞等待(用戶進程主動等待,並在拷貝時也等待)
  • NIO中將數據從內核空間拷貝到用戶空間時阻塞(用戶進程主動詢問,並在拷貝時等待)
  • IO Multiplexing中select等函數為阻塞、拷貝數據時也阻塞(用戶進程主動等待,並在拷貝時也等待)
  • AIO中從始至終用戶進程都沒有阻塞(用戶進程是被動的)

四 並發-並行-同步-異步-阻塞-非阻塞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 1 並發
並發是指一個時間段內,有幾個程序在同一個cpu上執行,但是同一時刻,只有一個程序在cpu上運行
跑步,鞋帶開了,停下跑步,系鞋帶
# 2 並行
指任意時刻點上,有多個程序同時運行在多個cpu上
跑步,邊跑步邊聽音樂
# 3 同步:
指代碼調用io操作時,必須等待io操作完成才返回的調用方式
# 4 異步
異步是指代碼調用io操作時,不必等io操作完成就返回調用方式
# 6 阻塞
指調用函數時候,當前線程別掛起
# 6 非阻塞
指調用函數時候,當前線程不會被掛起,而是立即返回

# 區別:
同步和異步是消息通訊的機制
阻塞和非阻塞是函數調用機制

五 IO設計模式

1
2
 Reactor模式,基於同步I/O實現
- Proactor模式,基於異步I/O實現

Reactor模式通常采用IO多路復用機制進行具體實現

1
- kqueue、epoll、poll、select等機制

Proactor模式通常采用OS Asynchronous IO(AIO)的異步機制進行實現

1
- 前提是對應操作系統支持AIO,比如支持異步IO的linux(不太成熟)、具備IOCP的windows server(非常成熟)

Reactor模式和Proactor模式都是事件驅動,主要實現步驟:

  1. 事件注冊:將事件與事件處理器進行分離。將事件注冊到事件循環中,將事件處理器單獨管理起來,記錄其與事件的對應關系。
  2. 事件監聽:啟動事件循環,一旦事件已經就緒/完成,就立刻通知事件處理器
  3. 事件分發:當收到事件就緒/完成的信號,便立刻激活與之對應的事件處理器
  4. 事件處理:在進程/線程/協程中執行事件處理器

使用過程中,用戶通常只負責定義事件和事件處理器並將其注冊以及一開始的事件循環的啟動,這個過程就會是以異步的形式執行任務。

Reactor模式

image-20200325235039094

Proactor模式

image-20200325235058486

對比分析

Reactor模型處理耗時長的操作會造成事件分發的阻塞,影響到后續事件的處理;

Proactor模型實現邏輯復雜;依賴操作系統對異步的支持,目前實現了純異步操作的操作系統少,實現優秀的如windows IOCP,但由於其windows系統用於服務器的局限性,目前應用范圍較小;而Unix/Linux系統對純異步的支持有限,因而應用事件驅動的主流還是基於select/epoll等實現的reactor模式

Python中:如asyncio、gevent、tornado、twisted等異步模塊都是依據事件驅動模型設計,更多的都是使用reactor模型,其中部分也支持proactor模式,當然需要根據當前運行的操作系統環境來進行手動配置


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM