TZ : 我是一個(gè)平凡的人,夢(mèng)想開一家甜品店
一 : 科普一分鐘
IO多路復(fù)用
是IO模式
的一種,是一種單線程處理多并發(fā)的IO
操作的方案,其他IO
操作方案分別有 :
- 阻塞 I/O(blocking IO)
- 非阻塞 I/O(nonblocking IO)
- I/O 多路復(fù)用( IO multiplexing)
- 異步 I/O(asynchronous IO)
IO多路復(fù)用
其實(shí)就是我們說(shuō)的select
寥院,poll
隘击,epoll
,它的基本原理就是select
执泰,poll
品抽,epoll
這個(gè)function會(huì)不斷的輪詢所負(fù)責(zé)的所有socket
杉女,當(dāng)某個(gè)socket
有數(shù)據(jù)到達(dá)了码耐,就通知用戶進(jìn)程巍举。
select
會(huì)輪詢檢測(cè)所有的連接,或者 IO
,其理論所使用的就是事件驅(qū)動(dòng)模型
這一編程范式
二 : 事件驅(qū)動(dòng)模型與IO多路復(fù)用關(guān)系
-
事件驅(qū)動(dòng)模型 :
好比中午全體員工1000人去飯,點(diǎn)餐完畢后我們回到座位,飯做好了服務(wù)員就會(huì)叫我們對(duì)應(yīng)點(diǎn)餐的人去取餐.
目前大部分的UI編程都是事件驅(qū)動(dòng)模型,如很多UI平臺(tái)都會(huì)提供onClick()事件蛋逾,這個(gè)事件就代表鼠標(biāo)按下事件集晚。事件驅(qū)動(dòng)模型大體思路如下:
- 有一個(gè)事件(消息)隊(duì)列;
- 鼠標(biāo)按下時(shí)换怖,往這個(gè)隊(duì)列中增加一個(gè)點(diǎn)擊事件(消息);
- 有個(gè)循環(huán)蟀瞧,不斷從隊(duì)列取出事件沉颂,根據(jù)不同的事件,調(diào)用不同的函數(shù)悦污,如onClick()铸屉、onKeyDown()等;
- 事件(消息)一般都各自保存各自的處理函數(shù)指針切端,這樣彻坛,每個(gè)消息都有獨(dú)立的處理函數(shù);
IO多路復(fù)用
所用的編程范式就是事件驅(qū)動(dòng)模型
,是一種監(jiān)測(cè)
和回調(diào)
封裝好的select
(拿select舉例)會(huì)不斷詢問處理,我們所放入的IO事件,哪個(gè)有數(shù)據(jù),就返回.我們就可以拿到了.
協(xié)程
也使用了事件驅(qū)動(dòng)模型
來(lái)繞過IO
事件,遇到IO
事件放入操作系統(tǒng)的讀取IO
列表,然后操作系統(tǒng)處理好給我們返回.
三 : 異步IO與同步IO
- 同步IO
1.阻塞 I/O(blocking IO)
2.非阻塞 I/O(nonblocking IO)
3.I/O 多路復(fù)用( IO multiplexing)
為什么會(huì)發(fā)生阻塞呢?其根本原因就是當(dāng)數(shù)據(jù)準(zhǔn)備好之后,內(nèi)核態(tài)
向用戶態(tài)
轉(zhuǎn)發(fā)的時(shí)候呼叫進(jìn)程來(lái)接受,這個(gè)轉(zhuǎn)化的過程是耗時(shí)的操作,等轉(zhuǎn)發(fā)結(jié)束后,阻塞接受
異步IO
為什么異步是沒有阻塞的操作呢,其根本原因也是因?yàn)?在內(nèi)核態(tài)
向用戶態(tài)
轉(zhuǎn)發(fā)結(jié)束后,才叫進(jìn)程來(lái)接收,進(jìn)程不用等待,直接拿到數(shù)據(jù).同步異步區(qū)別
同步IO
是快餐性質(zhì)
餐好了以后你過去取食物,拿食物回到座位,這個(gè)過程是耗時(shí)的操作.
異步IO
是大餐性質(zhì)
,我們餐好了以后,大家還在座位上聊天,服務(wù)員就會(huì)把食物端到桌子上,然后我們吃就可以了,高端服務(wù).
解析 :
我們可以用IO
多路復(fù)用寫一個(gè)SocketServer
實(shí)現(xiàn)單線程下的大并發(fā).
但是IO
多路復(fù)用本質(zhì)是還是同步IO
,因?yàn)閿?shù)據(jù)從內(nèi)核態(tài)
到用戶態(tài)
的拷貝需要等待操作系統(tǒng)完成,只有異步 I/O
本質(zhì)是是異步IO
,本質(zhì)上異步 I/O
會(huì)當(dāng)數(shù)據(jù)從內(nèi)核態(tài)
拷貝到用戶態(tài)
這一過程完成之后再去通知程序直接取走,因?yàn)?code>I/O操作是系統(tǒng)完成,所以這才是真正意義上的異步IO操作.
四 : IO多路復(fù)用socketServer
利用IO多路復(fù)用
寫一個(gè)socketServer
,大多數(shù)情況下機(jī)幾乎很難用到,因?yàn)橛性S許多多模塊和框架已經(jīng)為我們封裝好了,簡(jiǎn)單了解一下底層的實(shí)踐即可.
# Author:TianTianBaby
import select
import socket
import queue
#創(chuàng)建socket連接
server = socket.socket()
server.bind(('localhost',9000))
server.listen(1000)
#設(shè)置非阻塞模式
server.setblocking(False)
#key 為接受消息對(duì)象實(shí)例,key 為接受的數(shù)據(jù)
mes_dic = {}
#需要檢測(cè)的連接列表
inputs = [server,]
#返回上一次的數(shù)據(jù)列表
outputs = []
while True:
readable,writeable,exceptional = select.select(inputs,outputs,inputs)
for r in readable:
if r is server:#代表來(lái)了一個(gè)新連接
conn,addr = server.accept()
inputs.append(conn)#因?yàn)樾陆⒌倪B接還沒發(fā)數(shù)據(jù)過來(lái),現(xiàn)在就接受的話程序就報(bào)錯(cuò)
#所以要想實(shí)現(xiàn)這個(gè)客戶端發(fā)數(shù)據(jù)時(shí)server端能知道,就需要讓select再監(jiān)測(cè)
#初始化一個(gè)隊(duì)列,后面存要返回給這個(gè)客戶端的數(shù)據(jù)
mes_dic[conn] = queue.Queue()
else:
data = r.recv(1024)
mes_dic[r].put(data)
#放入返回的連接隊(duì)列里
outputs.append(r)
#要返回給客戶端的連接列表
for w in writeable:
data_to_client = mes_dic[w].get()
#返回給客戶端源數(shù)據(jù)
w.send(data_to_client)
#確保下次循環(huán)的時(shí)候writeable,不反回這個(gè)已經(jīng)處理完的連接
outputs.remove(w)
#異常
for e in exceptional:
if e in outputs:
outputs.remove(e)
inputs.remove(e)
del mes_dic[e]
五 : 總結(jié)
select
踏枣,poll
昌屉,epoll
都是IO多路復(fù)用
的機(jī)制。I/O多路復(fù)用
就是通過一種機(jī)制茵瀑,一個(gè)進(jìn)程可以監(jiān)視多個(gè)描述符间驮,一旦某個(gè)描述符就緒(一般是讀就緒或者寫就緒),能夠通知程序進(jìn)行相應(yīng)的讀寫操作马昨。但select
竞帽,poll
,epoll
本質(zhì)上都是同步I/O
鸿捧,因?yàn)樗麄兌夹枰谧x寫事件就緒后自己負(fù)責(zé)進(jìn)行讀寫屹篓,也就是說(shuō)這個(gè)讀寫過程是阻塞的,而異步I/O
則無(wú)需自己負(fù)責(zé)進(jìn)行讀寫匙奴,異步I/O
的實(shí)現(xiàn)會(huì)負(fù)責(zé)把數(shù)據(jù)從內(nèi)核
拷貝到用戶空間
堆巧。