python 并發(fā)編程 多路復(fù)用IO模型詳解
多路復(fù)用IO(IO multiplexing)
這種IO方式為事件驅(qū)動IO(event driven IO)。
我們都知道,select/epoll的好處就在于單個進(jìn)程process就可以同時處理多個網(wǎng)絡(luò)連接的IO。它的基本原理就是select/epoll這個function會不斷的輪詢所負(fù)責(zé)的所有socket,當(dāng)某個socket有數(shù)據(jù)到達(dá)了,就通知用戶進(jìn)程。它的流程如圖:

select是多路復(fù)用的一種
當(dāng)用戶進(jìn)程調(diào)用了select,那么整個進(jìn)程會被block,而同時,kernel會“監(jiān)視”所有select負(fù)責(zé)的socket,
當(dāng)任何一個socket中的數(shù)據(jù)準(zhǔn)備好了,select就會返回。這個時候用戶進(jìn)程再調(diào)用read操作,將數(shù)據(jù)從kernel拷貝到用戶進(jìn)程。
這個圖和blocking IO的圖其實(shí)并沒有太大的不同,事實(shí)上還更差一些。因?yàn)檫@里需要使用兩個系統(tǒng)調(diào)用\(select和recvfrom\),
而blocking IO只調(diào)用了一個系統(tǒng)調(diào)用\(recvfrom\)。但是,用select的優(yōu)勢在于它可以同時處理多個connection。
多路復(fù)用IO比較阻塞IO模型:
1.阻塞IO經(jīng)歷兩個階段 wait data,copy data
2.多路復(fù)用3個階段 wait data,ready copy data, copy data
單連接套接字通信 阻塞IO效率高
多路復(fù)用IO select可以代理多個套接字連接,多個套接字通信,多路復(fù)用IO效率高
強(qiáng)調(diào):
1. 如果處理的連接數(shù)不是很高的話,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延遲還更大。select/epoll的優(yōu)勢并不是對于單個連接能處理得更快,而是在于能處理更多的連接。
2. 在多路復(fù)用模型中,對于每一個socket,一般都設(shè)置成為non-blocking,但是,如上圖所示,整個用戶的process其實(shí)是一直被block的。只不過process是被select這個函數(shù)block,而不是被socket IO給block。
結(jié)論: select的優(yōu)勢在于可以處理多個連接,性能高,同時可以檢測多個套接字IO行為,不適用于單個連接
select網(wǎng)絡(luò)IO模型示例
select 檢測多個套接字IO行為 accept,recv
IO行為兩種:
1.別人給我傳數(shù)據(jù)
2.給別人發(fā)送數(shù)據(jù)
timeout是超時時間
每隔0.5秒去問操作系統(tǒng)準(zhǔn)備好數(shù)據(jù)沒有
def select(rlist, wlist, xlist, timeout=None): pass # [] 傳的空列表是出異常的列表 # 返回值3個列表 收列表,發(fā)列表,異常列表 rl,wl,xl = select.select(rlist, wlist, [], 0.5)
客戶端:
from socket import *
client = socket(AF_INET,SOCK_STREAM)
client.connect(('127.0.0.1',8000))
while True:
msg = input(">>>:").strip()
if not msg:continue
client.send(msg.encode("utf-8"))
data = client.recv(1024)
print(data.decode("utf-8"))
client.close()
服務(wù)端代碼:
from socket import *
import select
server = socket(AF_INET,SOCK_STREAM)
server.bind(('127.0.0.1',8000))
server.listen(5)
# 設(shè)置socket接口為 非阻塞IO接口
# 默認(rèn)是True 為阻塞
server.setblocking(False)
# 專門存著收消息套接字
rlist = [server,]
# 存放發(fā)送消息套接字
wlist = []
# 存放發(fā)送的數(shù)據(jù)
wdata = {}
while True:
# 返回值3個列表 收列表,發(fā)列表,異常列表
rl,wl,xl = select.select(rlist, wlist, [], 0.5)
print("rl",rl)
print("wl",wl)
for sock in rl:
if sock == server:
conn,addr = sock.accept()
rlist.append(conn)
else:
try:
data = sock.recv(1024)
if not data:
sock.close()
rlist.remove(sock)
continue
# 收的套接字加到列表
wlist.append(sock)
# 把數(shù)據(jù)加到字典 做一個 套接字對應(yīng)數(shù)據(jù)
wdata[sock] = data.upper()
except Exception:
sock.close()
rlist.remove(sock)
# 發(fā)送數(shù)據(jù)
for sock in wl:
sock.send(wdata[sock])
wlist.remove(sock)
wdata.pop(sock)
server.close()
基于select模塊 檢測套接字IO行為,實(shí)現(xiàn)并發(fā)效果
select監(jiān)聽fd變化的過程分析:
用戶進(jìn)程創(chuàng)建socket對象,拷貝監(jiān)聽的fd到內(nèi)核空間,每一個fd會對應(yīng)一張系統(tǒng)文件表,內(nèi)核空間的fd響應(yīng)到數(shù)據(jù)后,
就會發(fā)送信號給用戶進(jìn)程數(shù)據(jù)已到;
用戶進(jìn)程再發(fā)送系統(tǒng)調(diào)用,比如(accept)將內(nèi)核空間的數(shù)據(jù)copy到用戶空間,同時作為接受數(shù)據(jù)端內(nèi)核空間的數(shù)據(jù)清除,
這樣重新監(jiān)聽時fd再有新的數(shù)據(jù)又可以響應(yīng)到了(發(fā)送端因?yàn)榛赥CP協(xié)議所以需要收到應(yīng)答后才會清除)。
該模型的優(yōu)點(diǎn):
可以同時檢測多個套接字,效率比阻塞IO,非阻塞IO高了
相比其他模型,使用select() 的事件驅(qū)動模型只用單線程(進(jìn)程)執(zhí)行,占用資源少,不消耗太多 CPU,同時能夠?yàn)槎嗫蛻舳颂峁┓?wù)。
如果試圖建立一個簡單的事件驅(qū)動的服務(wù)器程序,這個模型有一定的參考價(jià)值。
該模型的缺點(diǎn):
代理的套接字 列表里的多個套接字,需要循環(huán)列表 一個個檢測,
在代理套接字比較少的情況下,循環(huán)比較快。但select代理的套接字非常多的情況下,select隨著列表增大,效率就越來越慢
首先select()接口并不是實(shí)現(xiàn)“事件驅(qū)動”的最好選擇。因?yàn)楫?dāng)需要探測的句柄值較大時,select()接口本身需要消耗大量時間去輪詢各個句柄。
很多操作系統(tǒng)提供了更為高效的接口,如linux提供了epoll,BSD提供了kqueue,Solaris提供了/dev/poll,…。
如果需要實(shí)現(xiàn)更高效的服務(wù)器程序,類似epoll這樣的接口更被推薦。遺憾的是不同的操作系統(tǒng)特供的epoll接口有很大差異,
所以使用類似于epoll的接口實(shí)現(xiàn)具有較好跨平臺能力的服務(wù)器會比較困難。
其次,該模型將事件探測和事件響應(yīng)夾雜在一起,一旦事件響應(yīng)的執(zhí)行體龐大,則對整個模型是災(zāi)難性的。
epoll是異步方式實(shí)現(xiàn),提交套接字時候,每個套接字身上都綁定一個回調(diào)函數(shù),哪個套接字準(zhǔn)備好了,就觸發(fā)回調(diào)函數(shù),把自己索引放在單獨(dú)列表里,對于select來說,只需要去準(zhǔn)備好的列表里 根據(jù)索引拿到套接字,這樣不需要在列表里每個遍歷。
epoll不支持windows系統(tǒng)
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Python Pygame實(shí)戰(zhàn)之實(shí)現(xiàn)經(jīng)營類游戲夢想小鎮(zhèn)代碼版
作為一名模擬經(jīng)營類游戲的發(fā)燒友,各種農(nóng)場類、醫(yī)院類、鐵路類的游戲玩兒了很多年。今天用代碼給大家打造一款夢想小鎮(zhèn)游戲,希望大家喜歡啦2022-12-12
在pycharm中為項(xiàng)目導(dǎo)入anacodna環(huán)境的操作方法
這篇文章主要介紹了在pycharm中為項(xiàng)目導(dǎo)入anacodna環(huán)境的操作方法,本文圖文并茂通過實(shí)例詳解的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-02-02
python中urllib.request和requests的使用及區(qū)別詳解
這篇文章主要介紹了python中urllib.request和requests的使用及區(qū)別詳解,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-05-05
Python中實(shí)現(xiàn)傳遞未知數(shù)量的函數(shù)參數(shù)
這篇文章主要介紹了Python中實(shí)現(xiàn)傳遞未知數(shù)量的函數(shù)參數(shù)方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-02-02
利用Opencv中Houghline方法實(shí)現(xiàn)直線檢測
這篇文章主要為大家詳細(xì)介紹了利用Opencv中的Houghline方法進(jìn)行直線檢測,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-02-02
機(jī)器學(xué)習(xí)、深度學(xué)習(xí)和神經(jīng)網(wǎng)絡(luò)之間的區(qū)別和聯(lián)系
機(jī)器學(xué)習(xí)>神經(jīng)網(wǎng)絡(luò)>深度學(xué)習(xí)≈深度神經(jīng)網(wǎng)絡(luò),機(jī)器學(xué)習(xí)包括了神經(jīng)網(wǎng)絡(luò)在內(nèi)的許多算法,而神經(jīng)網(wǎng)絡(luò)又可以分為淺度神經(jīng)網(wǎng)絡(luò)和深度神經(jīng)網(wǎng)絡(luò),深度學(xué)習(xí)是使用了深度神經(jīng)網(wǎng)絡(luò)的技術(shù),雖然機(jī)器學(xué)習(xí)、深度學(xué)習(xí)和神經(jīng)網(wǎng)絡(luò)是不同的,但在構(gòu)建復(fù)雜系統(tǒng)時,許多相關(guān)概念是混合在一起的2024-02-02
Python實(shí)現(xiàn)判斷一個整數(shù)是否為回文數(shù)算法示例
這篇文章主要介紹了Python實(shí)現(xiàn)判斷一個整數(shù)是否為回文數(shù)算法,結(jié)合實(shí)例形式分析了Python針對字符串的翻轉(zhuǎn)、判斷等相關(guān)操作技巧,需要的朋友可以參考下2019-03-03

