python 并發(fā)編程 多路復(fù)用IO模型詳解
多路復(fù)用IO(IO multiplexing)
這種IO方式為事件驅(qū)動(dòng)IO(event driven IO)。
我們都知道,select/epoll的好處就在于單個(gè)進(jìn)程process就可以同時(shí)處理多個(gè)網(wǎng)絡(luò)連接的IO。它的基本原理就是select/epoll這個(gè)function會(huì)不斷的輪詢所負(fù)責(zé)的所有socket,當(dāng)某個(gè)socket有數(shù)據(jù)到達(dá)了,就通知用戶進(jìn)程。它的流程如圖:
select是多路復(fù)用的一種
當(dāng)用戶進(jìn)程調(diào)用了select,那么整個(gè)進(jìn)程會(huì)被block,而同時(shí),kernel會(huì)“監(jiān)視”所有select負(fù)責(zé)的socket,
當(dāng)任何一個(gè)socket中的數(shù)據(jù)準(zhǔn)備好了,select就會(huì)返回。這個(gè)時(shí)候用戶進(jìn)程再調(diào)用read操作,將數(shù)據(jù)從kernel拷貝到用戶進(jìn)程。
這個(gè)圖和blocking IO的圖其實(shí)并沒(méi)有太大的不同,事實(shí)上還更差一些。因?yàn)檫@里需要使用兩個(gè)系統(tǒng)調(diào)用\(select和recvfrom\),
而blocking IO只調(diào)用了一個(gè)系統(tǒng)調(diào)用\(recvfrom\)。但是,用select的優(yōu)勢(shì)在于它可以同時(shí)處理多個(gè)connection。
多路復(fù)用IO比較阻塞IO模型:
1.阻塞IO經(jīng)歷兩個(gè)階段 wait data,copy data
2.多路復(fù)用3個(gè)階段 wait data,ready copy data, copy data
單連接套接字通信 阻塞IO效率高
多路復(fù)用IO select可以代理多個(gè)套接字連接,多個(gè)套接字通信,多路復(fù)用IO效率高
強(qiáng)調(diào):
1. 如果處理的連接數(shù)不是很高的話,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延遲還更大。select/epoll的優(yōu)勢(shì)并不是對(duì)于單個(gè)連接能處理得更快,而是在于能處理更多的連接。
2. 在多路復(fù)用模型中,對(duì)于每一個(gè)socket,一般都設(shè)置成為non-blocking,但是,如上圖所示,整個(gè)用戶的process其實(shí)是一直被block的。只不過(guò)process是被select這個(gè)函數(shù)block,而不是被socket IO給block。
結(jié)論: select的優(yōu)勢(shì)在于可以處理多個(gè)連接,性能高,同時(shí)可以檢測(cè)多個(gè)套接字IO行為,不適用于單個(gè)連接
select網(wǎng)絡(luò)IO模型示例
select 檢測(cè)多個(gè)套接字IO行為 accept,recv
IO行為兩種:
1.別人給我傳數(shù)據(jù)
2.給別人發(fā)送數(shù)據(jù)
timeout是超時(shí)時(shí)間
每隔0.5秒去問(wèn)操作系統(tǒng)準(zhǔn)備好數(shù)據(jù)沒(méi)有
def select(rlist, wlist, xlist, timeout=None): pass # [] 傳的空列表是出異常的列表 # 返回值3個(gè)列表 收列表,發(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) # 專門(mén)存著收消息套接字 rlist = [server,] # 存放發(fā)送消息套接字 wlist = [] # 存放發(fā)送的數(shù)據(jù) wdata = {} while True: # 返回值3個(gè)列表 收列表,發(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ù)加到字典 做一個(gè) 套接字對(duì)應(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模塊 檢測(cè)套接字IO行為,實(shí)現(xiàn)并發(fā)效果
select監(jiān)聽(tīng)fd變化的過(guò)程分析:
用戶進(jìn)程創(chuàng)建socket對(duì)象,拷貝監(jiān)聽(tīng)的fd到內(nèi)核空間,每一個(gè)fd會(huì)對(duì)應(yīng)一張系統(tǒng)文件表,內(nèi)核空間的fd響應(yīng)到數(shù)據(jù)后,
就會(huì)發(fā)送信號(hào)給用戶進(jìn)程數(shù)據(jù)已到;
用戶進(jìn)程再發(fā)送系統(tǒng)調(diào)用,比如(accept)將內(nèi)核空間的數(shù)據(jù)copy到用戶空間,同時(shí)作為接受數(shù)據(jù)端內(nèi)核空間的數(shù)據(jù)清除,
這樣重新監(jiān)聽(tīng)時(shí)fd再有新的數(shù)據(jù)又可以響應(yīng)到了(發(fā)送端因?yàn)榛赥CP協(xié)議所以需要收到應(yīng)答后才會(huì)清除)。
該模型的優(yōu)點(diǎn):
可以同時(shí)檢測(cè)多個(gè)套接字,效率比阻塞IO,非阻塞IO高了
相比其他模型,使用select() 的事件驅(qū)動(dòng)模型只用單線程(進(jìn)程)執(zhí)行,占用資源少,不消耗太多 CPU,同時(shí)能夠?yàn)槎嗫蛻舳颂峁┓?wù)。
如果試圖建立一個(gè)簡(jiǎn)單的事件驅(qū)動(dòng)的服務(wù)器程序,這個(gè)模型有一定的參考價(jià)值。
該模型的缺點(diǎn):
代理的套接字 列表里的多個(gè)套接字,需要循環(huán)列表 一個(gè)個(gè)檢測(cè),
在代理套接字比較少的情況下,循環(huán)比較快。但select代理的套接字非常多的情況下,select隨著列表增大,效率就越來(lái)越慢
首先select()接口并不是實(shí)現(xiàn)“事件驅(qū)動(dòng)”的最好選擇。因?yàn)楫?dāng)需要探測(cè)的句柄值較大時(shí),select()接口本身需要消耗大量時(shí)間去輪詢各個(gè)句柄。
很多操作系統(tǒng)提供了更為高效的接口,如linux提供了epoll,BSD提供了kqueue,Solaris提供了/dev/poll,…。
如果需要實(shí)現(xiàn)更高效的服務(wù)器程序,類(lèi)似epoll這樣的接口更被推薦。遺憾的是不同的操作系統(tǒng)特供的epoll接口有很大差異,
所以使用類(lèi)似于epoll的接口實(shí)現(xiàn)具有較好跨平臺(tái)能力的服務(wù)器會(huì)比較困難。
其次,該模型將事件探測(cè)和事件響應(yīng)夾雜在一起,一旦事件響應(yīng)的執(zhí)行體龐大,則對(duì)整個(gè)模型是災(zāi)難性的。
epoll是異步方式實(shí)現(xiàn),提交套接字時(shí)候,每個(gè)套接字身上都綁定一個(gè)回調(diào)函數(shù),哪個(gè)套接字準(zhǔn)備好了,就觸發(fā)回調(diào)函數(shù),把自己索引放在單獨(dú)列表里,對(duì)于select來(lái)說(shuō),只需要去準(zhǔn)備好的列表里 根據(jù)索引拿到套接字,這樣不需要在列表里每個(gè)遍歷。
epoll不支持windows系統(tǒng)
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Python3.7+tkinter實(shí)現(xiàn)查詢界面功能
這篇文章主要介紹了Python3.7+tkinter實(shí)現(xiàn)查詢界面功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-12-12Python+Pygame實(shí)現(xiàn)神廟逃亡游戲
這篇文章主要為大家介紹了如何利用Python和Pygame動(dòng)畫(huà)制作一個(gè)神廟逃亡類(lèi)似的小游戲。文中的示例代碼講解詳細(xì),感興趣的小伙伴可以動(dòng)手嘗試一下2022-05-05python入門(mén)基礎(chǔ)之用戶輸入與模塊初認(rèn)識(shí)
Python的強(qiáng)大之處在于他有非常豐富和強(qiáng)大的標(biāo)準(zhǔn)庫(kù)和第三方庫(kù),幾乎你想實(shí)現(xiàn)的任何功能都有相應(yīng)的Python庫(kù)支持。下面通過(guò)本文給大家介紹python入門(mén)基礎(chǔ)之用戶輸入與模塊初認(rèn)識(shí),一起看看吧2016-11-11python使用urllib2提交http post請(qǐng)求的方法
這篇文章主要介紹了python使用urllib2提交http post請(qǐng)求的方法,涉及Python使用urllib2模塊的相關(guān)技巧,需要的朋友可以參考下2015-05-05python爬蟲(chóng)之爬取谷歌趨勢(shì)數(shù)據(jù)
這篇文章主要介紹了python爬蟲(chóng)之爬取谷歌趨勢(shì)數(shù)據(jù),文中有非常詳細(xì)的代碼示例,對(duì)正在學(xué)習(xí)python爬蟲(chóng)的小伙伴們有非常好的幫助,需要的朋友可以參考下2021-04-04pytorch中F.avg_pool1d()和F.avg_pool2d()的使用操作
這篇文章主要介紹了pytorch中F.avg_pool1d()和F.avg_pool2d()的使用操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-05-05使用python框架Scrapy爬取數(shù)據(jù)的操作步驟
Scrapy是一個(gè)基于Python的強(qiáng)大的開(kāi)源網(wǎng)絡(luò)爬蟲(chóng)框架,用于從網(wǎng)站上抓取信息,它提供了廣泛的功能,使得爬取和分析數(shù)據(jù)變得相對(duì)容易,本文小編將給給大家介紹一下如何使用python框架Scrapy爬取數(shù)據(jù),需要的朋友可以參考下2023-10-10Python并發(fā)請(qǐng)求下限制QPS(每秒查詢率)的實(shí)現(xiàn)代碼
這篇文章主要介紹了Python并發(fā)請(qǐng)求下限制QPS(每秒查詢率)實(shí)現(xiàn)方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06Python調(diào)用百度AI實(shí)現(xiàn)人像分割詳解
本文主要介紹了如何通過(guò)Python調(diào)用百度AI從而實(shí)現(xiàn)人像的分割與合成,文中的示例代碼對(duì)我們的工作或?qū)W習(xí)有一定的幫助,需要的朋友可以參考一下2021-12-12