函數(shù)或方法掉調(diào)用的時(shí)候,被調(diào)用者是否能得到最終結(jié)果來(lái)判斷同步和異步
直接得到最終結(jié)果的,就是同步調(diào)用
不直接得到最終結(jié)果的,就是異步調(diào)用成都創(chuàng)新互聯(lián)公司專注于永德企業(yè)網(wǎng)站建設(shè),成都響應(yīng)式網(wǎng)站建設(shè)公司,商城網(wǎng)站制作。永德網(wǎng)站建設(shè)公司,為永德等地區(qū)提供建站服務(wù)。全流程按需策劃設(shè)計(jì),專業(yè)設(shè)計(jì),全程項(xiàng)目跟蹤,成都創(chuàng)新互聯(lián)公司專業(yè)和態(tài)度為您提供的服務(wù)
同步就是我讓你打飯,你不打好我就不走開(kāi),直到你打飯給了我
異步就是我讓你打飯,你等著,我不等你,但是我會(huì)盯著你,你打完我會(huì)過(guò)來(lái)拿走,異步并不能保證多長(zhǎng)時(shí)間將飯打完。異步給的是臨時(shí)結(jié)果,目前是拿不到的
同步只看結(jié)果是不是最終結(jié)果進(jìn)行判斷
函數(shù)或方法調(diào)用的時(shí)候,是否立即返回
立即返回就是非阻塞調(diào)用
不立即返回就是阻塞調(diào)用
同步,異步,阻塞,非阻塞 不相關(guān)
同步異步強(qiáng)調(diào)的是結(jié)果
阻塞,非阻塞強(qiáng)調(diào)的是時(shí)間,是否等待同步和異步的區(qū)別在于:調(diào)用者是否得到可想要的結(jié)果
同步就是一直要執(zhí)行到返回結(jié)果
異步就是直接返回了,但是不是最終結(jié)果,調(diào)用者不能通過(guò)這種調(diào)用方式得到結(jié)果,還是需要通過(guò)被調(diào)用者,使用其他方式通知調(diào)用者,來(lái)取回最終的結(jié)果
同步阻塞:我啥事也不干,就等你打飯給我,打飯是結(jié)果,而且我啥事也不敢就一直等,同步加阻塞。
同步非阻塞:我等著你打飯給我,但我可以完手機(jī),看電視,打飯是結(jié)果,但我不一直等
異步阻塞: 我要打飯,你說(shuō)等號(hào),并沒(méi)有給我返回飯,我啥事也不干,就等著飯好了叫我,叫號(hào)。
異步非阻塞:我要打飯,你說(shuō)等號(hào),并沒(méi)有返回飯,我在旁邊看電視,玩手機(jī),反打好了叫我。
1 數(shù)據(jù)準(zhǔn)備階段
2 內(nèi)核空間復(fù)制會(huì)用戶進(jìn)程緩沖區(qū)階段
1 內(nèi)核從輸入設(shè)備讀寫(xiě)數(shù)據(jù)
2 進(jìn)程從內(nèi)核復(fù)制數(shù)據(jù)
系統(tǒng)調(diào)用read 函數(shù)
第一個(gè)IO阻塞的函數(shù)是input函數(shù),是一個(gè)同步阻塞模型,網(wǎng)絡(luò)也是一個(gè)IO,標(biāo)準(zhǔn)輸入,標(biāo)準(zhǔn)輸出等也IO
CPU 不執(zhí)行拷貝數(shù)據(jù)從一個(gè)存儲(chǔ)區(qū)域到另一個(gè)存儲(chǔ)區(qū)域的任務(wù),這通常用于通過(guò)網(wǎng)絡(luò)傳輸一個(gè)文件時(shí)用于減少CPU周期和內(nèi)存帶寬。
操作系統(tǒng)某些組件(例如驅(qū)動(dòng)程序、文件系統(tǒng)和網(wǎng)絡(luò)協(xié)議棧)若采用零復(fù)制技術(shù),則能極大地增強(qiáng)了特定應(yīng)用程序的性能,并更有效地利用系統(tǒng)資源。通過(guò)使CPU得以完成其他而非將機(jī)器中的數(shù)據(jù)復(fù)制到另一處的任務(wù),性能也得到了增強(qiáng)。另外,零復(fù)制操作減少了在用戶空間與內(nèi)核空間之間切換模式的次數(shù)。
零復(fù)制協(xié)議對(duì)于網(wǎng)絡(luò)鏈路容量接近或超過(guò)CPU處理能力的高速網(wǎng)絡(luò)尤為重要。在這種網(wǎng)絡(luò)下,CPU幾乎將所有時(shí)間都花在復(fù)制要傳送的數(shù)據(jù)上,因此將成為使通信速率低于鏈路容量的瓶頸。
1 減少甚至完全避免不必要的CPU拷貝,從而讓CPU 解脫出來(lái)去執(zhí)行其他任務(wù)
2 減少內(nèi)存帶寬占用
3 通常零拷貝技術(shù)還能減少用戶空間和內(nèi)核空間之間的上下文切換
從Linux系統(tǒng)來(lái)看,除了引導(dǎo)系統(tǒng)的BIN區(qū),整個(gè)內(nèi)存空間主要被分成兩部分:
1 內(nèi)核空間(kernel space ) : 主要提供給程序調(diào)度,內(nèi)存分配,連接硬件資源等程序邏輯空間
2 用戶空間 (user space): 提供給各個(gè)進(jìn)程的主要空間,用戶空間不具備訪問(wèn)內(nèi)核空間資源的權(quán)限,因此如果應(yīng)用程序需要使用到內(nèi)核空間的資源,則需要通過(guò)系統(tǒng)調(diào)度來(lái)完成,從用戶空間切換到內(nèi)核空間,然后在完成操作后再?gòu)膬?nèi)核空間切換到用戶空間
1 直接I/O: 對(duì)于這種傳輸方式來(lái)說(shuō),應(yīng)用程序可以直接訪問(wèn)硬件存儲(chǔ),操作系統(tǒng)內(nèi)核只是輔助數(shù)據(jù)傳輸,這種方式依舊存在用戶空間和內(nèi)核空間的上下文切換,但硬件上的數(shù)據(jù)不會(huì)拷貝到內(nèi)核空間,而是直接拷貝到可用戶空間,因此直接IO不存在內(nèi)核空間緩沖區(qū)和用戶空間緩沖區(qū)之間的數(shù)據(jù)拷貝
2 在數(shù)據(jù)傳輸過(guò)程中,避免數(shù)據(jù)在用戶空間緩沖區(qū)和內(nèi)核空間緩沖區(qū)之間的CPU拷貝,以及數(shù)據(jù)在系統(tǒng)內(nèi)核空間的CPU拷貝,
3 copy-on-write(寫(xiě)時(shí)復(fù)制技術(shù)):在某些情況下,Linux操作系統(tǒng)的內(nèi)核緩沖區(qū)可能被多個(gè)應(yīng)用程序共享,操作系統(tǒng)有可能會(huì)將用戶空間緩沖區(qū)地址映射考內(nèi)核空間緩沖區(qū),當(dāng)應(yīng)用程序需要對(duì)共享的數(shù)據(jù)進(jìn)行修改時(shí),才需要真正的拷貝數(shù)據(jù)到應(yīng)用程序的用戶空間緩沖區(qū)中,并且對(duì)自己的用戶空間的緩沖區(qū)的數(shù)據(jù)進(jìn)行修改不會(huì)影響到其他共享數(shù)據(jù)的應(yīng)用程序,所以,如果應(yīng)用程序不需要對(duì)數(shù)據(jù)進(jìn)行任何修改,就不會(huì)存在數(shù)據(jù)從系統(tǒng)內(nèi)核空間緩沖區(qū)拷貝到用戶空間緩沖區(qū)的操作。
對(duì)于零拷貝技術(shù)是否實(shí)現(xiàn)主要依賴于操作系統(tǒng)底層是否提供相應(yīng)的支持。
1 發(fā)起read系統(tǒng)調(diào)用: 導(dǎo)致用戶空間到內(nèi)核空間的上下文切換(第一次上下文切換),通過(guò)DMA引擎將文件中的數(shù)據(jù)從磁盤(pán)上讀取到內(nèi)核空間緩沖區(qū)(第一次拷貝:hand drive ----> kernel buffer)
2 將內(nèi)核空間緩沖區(qū)的數(shù)據(jù)拷貝到用戶空間緩沖區(qū)中(第二次拷貝: kernel buffer ---> user buffer),然后read系統(tǒng)調(diào)用返回,而系統(tǒng)調(diào)用的返回又會(huì)導(dǎo)致一次內(nèi)核空間到用戶空間的上下文切換(第二次上下文切換)
3 發(fā)出write系統(tǒng)調(diào)用: 導(dǎo)致用戶空間到內(nèi)核空間的上下文切換(第三次上下文切換),將用戶空間緩沖區(qū)的數(shù)據(jù)拷貝到內(nèi)核空間中于socket相關(guān)的緩沖區(qū)中,(及第二步從內(nèi)核空間緩沖區(qū)拷貝的數(shù)據(jù)原封不動(dòng)的再次拷貝到內(nèi)核空間的socket緩沖區(qū)中)( 第三次拷貝: user buffer--> socket buffer)
4 write 系統(tǒng)調(diào)用返回,導(dǎo)致內(nèi)核空間到用戶空間的再次上下文切換(第四次上下文切換),通過(guò)DMA引擎將內(nèi)核緩沖區(qū)中的數(shù)據(jù)傳遞到協(xié)議引擎(第四次拷貝:socket buffer -> protocol engine ),這次拷貝時(shí)獨(dú)立的異步的過(guò)程。
事實(shí)上調(diào)用的返回并不保證數(shù)據(jù)被傳輸,甚至不保證數(shù)據(jù)傳輸?shù)拈_(kāi)始,只是意味著將我么要發(fā)送的數(shù)據(jù)放入到了一個(gè)待發(fā)送的隊(duì)列中,除非實(shí)現(xiàn)了優(yōu)先環(huán)或者隊(duì)列,否則會(huì)是先進(jìn)先出的方式發(fā)送數(shù)據(jù)的。
總的來(lái)說(shuō),傳統(tǒng)的I/O操作進(jìn)行了4次用戶空間與內(nèi)核空間的上下文切換,以及4次數(shù)據(jù)拷貝。其中4次數(shù)據(jù)拷貝中包括了2次DMA拷貝和2次CPU拷貝。
傳統(tǒng)模式為何將數(shù)據(jù)從磁盤(pán)讀取到內(nèi)核空間而不是直接讀取到用戶空間緩沖區(qū),其原因是為了減少I(mǎi)O操作以提高性能,因?yàn)镺S會(huì)根據(jù)局部性原理一次read() 系統(tǒng)調(diào)用的時(shí)候預(yù)讀取更多的文件數(shù)據(jù)到內(nèi)核空間緩沖區(qū)中,這樣當(dāng)下一次read()系統(tǒng)調(diào)用的時(shí)候發(fā)現(xiàn)要讀取的數(shù)據(jù)已經(jīng)存在于內(nèi)核空間緩沖區(qū)的時(shí)候只需要直接拷貝數(shù)據(jù)到用戶空間緩沖區(qū)即可,無(wú)需再進(jìn)行一次低效的磁盤(pán)IO操作。
Bufferedinputstream 作用是會(huì)根據(jù)情況自動(dòng)為我們預(yù)讀取更多的數(shù)據(jù)到他自己維護(hù)的一個(gè)內(nèi)部字節(jié)數(shù)據(jù)緩沖區(qū),這樣能減少系統(tǒng)調(diào)用次數(shù)來(lái)提高性能。
總的來(lái)說(shuō),內(nèi)核緩沖區(qū)的一大作用是為了減少磁盤(pán)IO操做,Bufferedinputstream 則是減少"系統(tǒng)調(diào)用"
DMA(direct memory access) --- 直接內(nèi)存訪問(wèn),DMA 是允許外設(shè)組件將IO數(shù)據(jù)直接傳送到主存儲(chǔ)器并并且傳輸不需要CPU參與,以此解放CPU去做其他的事情。
而用戶空間與內(nèi)核空間之間的數(shù)據(jù)傳輸并沒(méi)有類似DMA這種可以不需要CPU參與的傳輸工具,因此用戶空間與內(nèi)核空間之間的數(shù)據(jù)傳輸是需要CPU全程參與的。所有也就有了通過(guò)零拷貝技術(shù)來(lái)減少和避免不必要的CPU數(shù)據(jù)拷貝過(guò)程。
1 發(fā)起sendfile系統(tǒng)調(diào)用,導(dǎo)致用戶空間到內(nèi)核空間的上下文切換(第一次上下文切換),通過(guò)DMA引擎將磁盤(pán)文件中的內(nèi)容拷貝到內(nèi)核空間緩沖區(qū)中(第一次拷貝: hard drive --> kernel buffer)然后再將數(shù)據(jù)從內(nèi)核空間拷貝到socket相關(guān)的緩沖區(qū)中,(第二次拷貝,kernel ---buffer --> socket buffer)
2 sendfile 系統(tǒng)調(diào)用返回,導(dǎo)致內(nèi)核空間到用戶空間的上下文切換(第二次上下文切換)。通過(guò)DMA 引擎將內(nèi)核空間的socket緩沖區(qū)的數(shù)據(jù)傳遞到協(xié)議引擎(第三次拷貝:socket buffer-> protocol engine )
總的來(lái)說(shuō),通過(guò)sendfile實(shí)現(xiàn)的零拷貝I/O只使用了2次用戶空間與內(nèi)核空間的上下文切換,以及3次數(shù)據(jù)的拷貝。其中3次數(shù)據(jù)拷貝中包括了2次DMA拷貝和1次CPU拷貝。
Q:但通過(guò)是這里還是存在著一次CPU拷貝操作,即,kernel buffer ——> socket buffer。是否有辦法將該拷貝操作也取消掉了?
A:有的。但這需要底層操作系統(tǒng)的支持。從Linux 2.4版本開(kāi)始,操作系統(tǒng)底層提供了scatter/gather這種DMA的方式來(lái)從內(nèi)核空間緩沖區(qū)中將數(shù)據(jù)直接讀取到協(xié)議引擎中,而無(wú)需將內(nèi)核空間緩沖區(qū)中的數(shù)據(jù)再拷貝一份到內(nèi)核空間socket相關(guān)聯(lián)的緩沖區(qū)中。
從Linux 2.4 開(kāi)始,操做系統(tǒng)底層提供了帶有scatter/gather 的DMA來(lái)從內(nèi)核空間緩沖區(qū)中將數(shù)據(jù)讀取到協(xié)議引擎中,這樣以來(lái)待傳輸?shù)臄?shù)據(jù)可以分散再存儲(chǔ)的不同位置,而不需要再連續(xù)存儲(chǔ)中存放,那么從文件中讀出的數(shù)據(jù)就根本不需要被拷貝到socket緩沖區(qū)中去,只是需要將緩沖區(qū)描述符添加到socket緩沖區(qū)中去,DMA收集操作會(huì)根據(jù)緩沖區(qū)描述符中的信息將內(nèi)核空間中的數(shù)據(jù)直接拷貝到協(xié)議引擎中
1 發(fā)出sendfile 系統(tǒng)調(diào)用,導(dǎo)致用戶空間到內(nèi)核空間的上下文切換,通過(guò)DMA 引擎將磁盤(pán)文件內(nèi)容拷貝到內(nèi)核空間緩沖區(qū)中(第一次拷貝: hard drive -> kernel buffer)
2 沒(méi)有數(shù)據(jù)拷貝到socket緩沖區(qū),取而代之的是只有向相應(yīng)的描述信息被拷貝到相應(yīng)的socket緩沖區(qū)中,該描述信息包含了兩個(gè)方面: 1 kernel buffer 的內(nèi)存地址 2 kernel buffer 的偏移量。
3 sendfile 系統(tǒng)調(diào)用返回,導(dǎo)致內(nèi)核空間到用戶空間的上下文切換(第二次上下文切換),DMA gather copy 根據(jù) socket緩沖區(qū)中描述符提供的位置和偏移量信息直接將內(nèi)核空間的數(shù)據(jù)拷貝到協(xié)議引擎上(kernel buffer --> protocol engine),這樣就避免了最后依次CPU數(shù)據(jù)拷貝
總的來(lái)說(shuō),帶有DMA收集拷貝功能的sendfile實(shí)現(xiàn)的I/O只使用了2次用戶空間與內(nèi)核空間的上下文切換,以及2次數(shù)據(jù)的拷貝,而且這2次的數(shù)據(jù)拷貝都是非CPU拷貝。這樣一來(lái)我們就實(shí)現(xiàn)了最理想的零拷貝I/O傳輸了,不需要任何一次的CPU拷貝,以及最少的上下文切換。
在Linux 2.6.33 版本之前sendfile支持文件到套接字之間的傳輸,及in_fd 相當(dāng)于一個(gè)支持mmap的文件,out_fd 必須是一個(gè)socket,但從Linux 2.6.33版本開(kāi)始,out_fd 可以是任意類型文件描述符,所以從Linux 2.6.33 版本開(kāi)始sendfile 可以支持文件到文件,文件到套接字之間的數(shù)據(jù)傳輸。
傳統(tǒng)I/O通過(guò)兩條系統(tǒng)指令read、write來(lái)完成數(shù)據(jù)的讀取和傳輸操作,以至于產(chǎn)生了4次用戶空間與內(nèi)核空間的上下文切換的開(kāi)銷;而sendfile只使用了一條指令就完成了數(shù)據(jù)的讀寫(xiě)操作,所以只產(chǎn)生了2次用戶空間與內(nèi)核空間的上下文切換。
傳統(tǒng)I/O產(chǎn)生了2次無(wú)用的CPU拷貝,即內(nèi)核空間緩存中數(shù)據(jù)與用戶空間緩沖區(qū)間數(shù)據(jù)的拷貝;而sendfile最多只產(chǎn)出了一次CPU拷貝,即內(nèi)核空間內(nèi)之間的數(shù)據(jù)拷貝,甚至在底層操作體系支持的情況下,sendfile可以實(shí)現(xiàn)零CPU拷貝的I/O。
因傳統(tǒng)I/O用戶空間緩沖區(qū)中存有數(shù)據(jù),因此應(yīng)用程序能夠?qū)Υ藬?shù)據(jù)進(jìn)行修改等操作;而sendfile零拷貝消除了所有內(nèi)核空間緩沖區(qū)與用戶空間緩沖區(qū)之間的數(shù)據(jù)拷貝過(guò)程,因此sendfile零拷貝I/O的實(shí)現(xiàn)是完成在內(nèi)核空間中完成的,這對(duì)于應(yīng)用程序來(lái)說(shuō)就無(wú)法對(duì)數(shù)據(jù)進(jìn)行操作了。
Q:對(duì)于上面的第三點(diǎn),如果我們需要對(duì)數(shù)據(jù)進(jìn)行操作該怎么辦了?
A:Linux提供了mmap零拷貝來(lái)實(shí)現(xiàn)我們的需求
Mmap(內(nèi)存映射)是一個(gè)比sendfile昂貴但優(yōu)于傳統(tǒng)IO的方式
1 發(fā)出mmap系統(tǒng)調(diào)用,導(dǎo)致用戶空間到內(nèi)核空間的上下文切換(第一次上下文切換)。通過(guò)DMA引擎將磁盤(pán)文件中的內(nèi)容拷貝到內(nèi)核空間緩沖區(qū)中(第一次拷貝: hard drive ——> kernel buffer)。
2 mmap 系統(tǒng)調(diào)用返回,導(dǎo)致內(nèi)核空間到用戶空間的上下文切換(第二次上下文切換),接著用戶空間和內(nèi)核空間共享這個(gè)緩沖區(qū),而不需要將數(shù)據(jù)從內(nèi)核空間拷貝到用戶空間,因此用戶空間和內(nèi)核空間共享的緩沖區(qū)
3 發(fā)出write 系統(tǒng)調(diào)用紅,導(dǎo)致用戶空間到內(nèi)核空間第三次上下文切換,將數(shù)據(jù)從內(nèi)核空間拷貝到內(nèi)核空間的socket相關(guān)的緩沖區(qū)(第二次拷貝:kernel buffer ----> socket buffer )
4 write 系統(tǒng)調(diào)用返回,導(dǎo)致內(nèi)核空間到用戶空間的上下文切換(第四次上下文切換),通過(guò)DMA 引擎將內(nèi)核空間socket緩沖區(qū)的數(shù)據(jù)傳遞到協(xié)議引擎(第三次拷貝: socket buffer---> protocol engine)
總的來(lái)說(shuō),通過(guò)mmap實(shí)現(xiàn)的零拷貝I/O進(jìn)行了4次用戶空間與內(nèi)核空間的上下文切換,以及3次數(shù)據(jù)拷貝。其中3次數(shù)據(jù)拷貝中包括了2次DMA拷貝和1次CPU拷貝。
在文件讀取進(jìn)入內(nèi)核空間和從內(nèi)核空間拷貝進(jìn)入用戶進(jìn)程空間的過(guò)程中,沒(méi)有任何的數(shù)據(jù)返回,客戶端在一直等待狀態(tài)。
進(jìn)程調(diào)用read操作,如果IO沒(méi)有準(zhǔn)備好,立即返回ERROR,進(jìn)程不阻塞,用戶可以再次發(fā)起系統(tǒng)調(diào)用,如果內(nèi)核已經(jīng)準(zhǔn)備好,就阻塞,然后復(fù)制數(shù)據(jù)到用戶空間
第一階段數(shù)據(jù)沒(méi)準(zhǔn)備好,就先忙別的,等會(huì)再看看,檢查數(shù)據(jù)是否準(zhǔn)備好了的過(guò)程是非阻塞的
第二階段是阻塞的,及內(nèi)核空間和用戶空間之間復(fù)制數(shù)據(jù)是阻塞的,但是要等待飯盛好才是完事,這是同步的。
所謂的IO多路復(fù)用,就是同時(shí)監(jiān)控多個(gè)IO,有一個(gè)準(zhǔn)備好了,就不需要等待開(kāi)始處理,提高了同時(shí)處理IO的能力
select是所有平臺(tái)都支持,poll是對(duì)select的升級(jí)
epoll,Linux 系統(tǒng)內(nèi)核2.5+ 開(kāi)始支持,對(duì)select和epoll的增強(qiáng),在監(jiān)視的基礎(chǔ)上,增加了回調(diào)機(jī)制,BSD,Mac的kqueue,還有windows的iocp
如果既想訪問(wèn)網(wǎng)絡(luò),又想訪問(wèn)文件,則先將準(zhǔn)備好的數(shù)據(jù)先處理,那個(gè)準(zhǔn)備好了就處理那個(gè)
能夠提高同時(shí)處理IO的能力,誰(shuí)先做玩我先處理誰(shuí)
上面的兩種方式,效率太差了,等完一個(gè)完成后再等一個(gè),太慢了。
誰(shuí)好了處理誰(shuí),不同的平臺(tái)對(duì)IO多路復(fù)用的實(shí)現(xiàn)方式是不同的
Select 和 poll 在Linux,Windows,和MAC中都支持
一般來(lái)將select和poll 在同一個(gè)層次,epoll是Linux中存在的
select原理
1 將關(guān)注的IO操作告訴select函數(shù)并調(diào)用,進(jìn)程阻塞,內(nèi)核監(jiān)視select關(guān)注的文件,描述符FD,被關(guān)注的任何一個(gè)FD對(duì)應(yīng)的IO準(zhǔn)備好了數(shù)據(jù),select就返回,在使用read將數(shù)據(jù)復(fù)制到用用戶進(jìn)程。其select模式下的準(zhǔn)備好的通知是沒(méi)有針對(duì)性的,需要用戶自己找到是否是自己的并進(jìn)行處理。select做到的是時(shí)間重疊
epoll增加了回調(diào)機(jī)制,那一路準(zhǔn)備好了,我會(huì)告訴你,有一種是你不用管了,好了我直接替你調(diào)用。
兩個(gè)階段
等待數(shù)據(jù)準(zhǔn)備和拷貝階段
立即返回?cái)?shù)據(jù),給一個(gè)號(hào)。到時(shí)候叫號(hào),直接返回
信號(hào)句柄,告訴你幾號(hào)好了,(signal handler process datagram)
有些時(shí)候是需要爭(zhēng)搶的
我可以不通知你,我也可以通知你后你再來(lái)
理解數(shù)據(jù)層面的東西,就不要理解其他的socket層面的東西
文件中實(shí)際就是兩個(gè)緩沖隊(duì)列,每個(gè)隊(duì)列是一個(gè)。
在異步模型中,操作系統(tǒng)通你的,你是在用戶空間的,操作系統(tǒng)可以是在內(nèi)核空間的,進(jìn)程和線程等等的都是操作系統(tǒng)層面的東西。
整個(gè)過(guò)程中進(jìn)程都可以做其他的事,就算是通知了,也不一定要立即反應(yīng),這和你的設(shè)置有關(guān)
Linux中的AIO 的系統(tǒng)調(diào)用,內(nèi)核版本從2.6開(kāi)始支持
一般的IO是IO多路復(fù)用和異步復(fù)用
IO 多路復(fù)用
大多數(shù)操作系統(tǒng)都支持select和poll
Linux 2.5+ 支持epoll
BSD,Mac支持kqueue
Windows 的 iocp
python的select庫(kù)實(shí)現(xiàn)了select,poll系統(tǒng)調(diào)用,這個(gè)基本上操作系統(tǒng)都支持,部分實(shí)現(xiàn)了epoll,底層的IO多路復(fù)用模塊
開(kāi)發(fā)中的選擇
1 完全跨平臺(tái),select 和poll ,但其性能較差
2 針對(duì)不同的操作系統(tǒng)自行選擇支持技術(shù),這樣會(huì)提高IO處理能力selectors庫(kù)
3.4 版本后提供這個(gè)庫(kù),高級(jí)的IO復(fù)用庫(kù)
類層次結(jié)構(gòu)BaseSelector
+-- SelectSelector 實(shí)現(xiàn)select
+-- PollSelector 實(shí)現(xiàn)poll
+-- EpollSelector 實(shí)現(xiàn)epoll
+-- DevpollSelector 實(shí)現(xiàn)devpoll
+-- KqueueSelector 實(shí)現(xiàn)kqueueselectors.DefaultSelector返回當(dāng)前平臺(tái)最有效,性能最最高的實(shí)現(xiàn)
但是由于沒(méi)有實(shí)現(xiàn)windows的IOCP,所以只能退化為select。
默認(rèn)會(huì)自適應(yīng),其會(huì)選擇最佳的方式,Linux 會(huì)直接選擇 epoll ,通過(guò)此處,能拿到平臺(tái)的最優(yōu)方案。
DefaultSelector 源碼
if 'KqueueSelector' in globals():
DefaultSelector = KqueueSelector
elif 'EpollSelector' in globals():
DefaultSelector = EpollSelector
elif 'DevpollSelector' in globals():
DefaultSelector = DevpollSelector
elif 'PollSelector' in globals():
DefaultSelector = PollSelector
else:
DefaultSelector = SelectSelector
abstractmethod register(fileobj,events,data=None)
為selection注冊(cè)一個(gè)文件獨(dú)享,監(jiān)視它的IO事件
fileobj 被監(jiān)視的文件對(duì)象,如socket對(duì)象
events 事件,該文件對(duì)象必須等待的事件,read或write
data 可選的與此文件對(duì)象相關(guān)的不透明數(shù)據(jù),如可用來(lái)存儲(chǔ)每個(gè)客戶端的會(huì)話ID,可以是函數(shù),類,實(shí)例,如果是函數(shù),有點(diǎn)回調(diào)的意思,通知某個(gè)函數(shù),某個(gè)實(shí)例,某個(gè)類,可以是類屬性,等,都可以,None表示消息發(fā)生了,沒(méi)人認(rèn)領(lǐng)。
第一步 :需要實(shí)例化 ,選擇一個(gè)最優(yōu)的實(shí)現(xiàn),將其實(shí)例化(選擇不同平臺(tái)實(shí)現(xiàn)的IO復(fù)用的最佳框架),python內(nèi)部處理
第二步:注冊(cè)函數(shù),將要監(jiān)控對(duì)象,要監(jiān)控事件和監(jiān)控觸發(fā)后對(duì)象寫(xiě)入register注冊(cè)中
1 注冊(cè): 對(duì)象,啥事件,調(diào)用的函數(shù)
2 進(jìn)行循環(huán)和監(jiān)控select函數(shù)的返回,當(dāng)監(jiān)控的對(duì)象的事件滿足時(shí)會(huì)立即返回,在events中可以拿到這些數(shù)據(jù)events中有我是誰(shuí),我是什么事件觸發(fā)的(讀和寫(xiě)),讀的滿足可以recv,key 是讓我監(jiān)控的東西,event是其什么事件觸發(fā)的。將對(duì)象和事件拿到后做相應(yīng)的處理。
第三步:實(shí)時(shí)關(guān)注socket有讀寫(xiě)操作,從而影響events的變化
對(duì)socket來(lái)判斷有沒(méi)有讀,若讀了,則直接觸發(fā)對(duì)應(yīng)的機(jī)制進(jìn)行處理。一旦有新的連接準(zhǔn)備,則會(huì)將其消息發(fā)送給對(duì)應(yīng)的函數(shù)進(jìn)行處理相關(guān)的操作。被調(diào)用的函數(shù)是有要求的,其需要傳送mask的,data 就是未來(lái)要調(diào)用的函數(shù),建立了事件和未來(lái)參數(shù)之間建立的關(guān)系。
Accept 本身就是一個(gè)read事件
Selector 會(huì)調(diào)用自己的select函數(shù)進(jìn)行監(jiān)視,這個(gè)函數(shù)是阻塞的,當(dāng)數(shù)據(jù)已經(jīng)在內(nèi)核緩沖區(qū)準(zhǔn)備好了,你就可以讀取了,這些事給select進(jìn)行處理在注冊(cè)的時(shí)候,后面加了data,后面直接使用,直接調(diào)用,不用管其他,data和每一個(gè)觀察者直接對(duì)應(yīng)起來(lái)的。
只要有一個(gè)滿足要求,直接返回
讀事件指的是in操作,及就是當(dāng)有連接的時(shí)候
當(dāng)通知成功后,其函數(shù)內(nèi)部是不會(huì)阻塞了,等待通知,通知成功后就不會(huì)阻塞了。此處的data相當(dāng)于直接帶著窗口號(hào),直接進(jìn)行處理,而不需要一個(gè)一個(gè)的遍歷
當(dāng)一個(gè)滿足了,就不會(huì)阻塞了。events: 兩個(gè)IO都滿足,等待幾路,幾路的IO都在此處,如果滿足,則直接向下打印events,其中key是注冊(cè)的唯一的東西,socket 也可以,但是可以定義socket的讀和寫(xiě),一般都是合著的
第四步:調(diào)用對(duì)應(yīng)事件的對(duì)象,并執(zhí)行相關(guān)操作
然后將events拿出來(lái)解構(gòu),key本身是一個(gè)多元祖,key上保存著注冊(cè)塞進(jìn)去的data,key是存儲(chǔ)了4個(gè)信息的元祖,此處的data稱為回調(diào)函數(shù),加上() 稱為調(diào)用
代碼下載目錄
IO 多路復(fù)用初始代碼
https://pan.baidu.com/s/18B5OL89Z4YSxEmX4gNkgDA
2019-09-01 09:37:46 Thread-1 events: [(SelectorKey(fileobj=
, fd=4, events=1, data= ), 1)] events中包含了兩組
第一組 :
fileobj 及套接字返回的相關(guān)參數(shù),和之前的socket中的accpet中的conn 相似,fd 及文件描述符
events 及事件類型,兩種
data 及注冊(cè)調(diào)用的函數(shù),上述的有accept 和recv 函數(shù)
第二組:
1 events 的狀態(tài),及mask
1 select.get_map().items() 中的key
2019-09-01 09:43:52 MainThread key:SelectorKey(fileobj=, fd=4, events=1, data= ) 此處的key和上面的列表中的二元祖中的前一個(gè)完全相同
2 select.get_map().items() 中的fobj
2019-09-01 09:43:52 MainThread fobj: 4
其是其中的文件描述符
IO 多路復(fù)用就是一個(gè)線程來(lái)處理所有的IO
在單線程中進(jìn)行處理IO多路復(fù)用
多線程中的IO阻塞時(shí)浪費(fèi)CPU資源,其是等待狀態(tài),等待狀態(tài)雖然不占用CPU資源,但線程本身的狀態(tài)需要維持,還是會(huì)占用一定的資源
send 是寫(xiě)操作,有可能阻塞,也可以監(jiān)聽(tīng)
recv所在的注冊(cè)函數(shù),要監(jiān)聽(tīng)讀與寫(xiě)事件,回調(diào)的時(shí)候,需要mask 來(lái)判斷究竟是讀觸發(fā)了還是寫(xiě)觸發(fā)了,所以,需要修改方法聲明,增加mask
寫(xiě)操作當(dāng)發(fā)送群聊時(shí),其每個(gè)鏈接是獨(dú)立的,需要queue隊(duì)列保存相關(guān)的數(shù)據(jù),并進(jìn)行接受和發(fā)送操作
IO 多路復(fù)用最終代碼
https://pan.baidu.com/s/1y-3j607_5DxBpa4wZNxCEQ
3.4 版本加入標(biāo)準(zhǔn)庫(kù)
asyncio 底層是基于selectors實(shí)現(xiàn)的,看似庫(kù),其實(shí)就是一個(gè)框架,包括異步IO,事件循環(huán),協(xié)程,任務(wù)等
并行和串行的區(qū)分:
兩個(gè)事件的因果關(guān)系:
若有因果關(guān)系,則可以使用串行
若無(wú)因果關(guān)系,則可以使用并行,及多線程來(lái)處理
參數(shù) | 含義 |
---|---|
asyncio.get_event_loop() | 返回一個(gè)事件循環(huán)對(duì)象,是asyncio.BaseEventLoop的實(shí)例 |
AbstractEventLoop.stop() | 停止運(yùn)行事件循環(huán) |
AbstractEventLoop.run_forever() | 一直運(yùn)行,直到stop() |
AbstractEventLoop.run_until_complete(future) | 運(yùn)行直到Future對(duì)象運(yùn)行完成 |
AbstractEventLoop.close() | 關(guān)閉事件循環(huán) |
AbstractEventLoop.is_running() | 返回事件循環(huán)是否運(yùn)行 |
AbstractEventLoop.close() | 關(guān)閉事件 |
#!/usr/bin/poython3.6
#conding:utf-8
import threading
def a():
for i in range(3):
print (i)
def b():
for i in "abc":
print (i)
a()
b()
此處的默認(rèn)執(zhí)行順序是a()到b()的順序執(zhí)行,若要使其交叉執(zhí)行,則需要使用yield 來(lái)實(shí)現(xiàn)
實(shí)現(xiàn)方式如下
#!/usr/bin/poython3.6
#conding:utf-8
import threading
import multiprocessing
def a():
for i in range(3):
print (i)
yield
def b():
for i in "abc":
print (i)
yield
a=a()
b=b()
for i in range(3):
next(a)
next(b)
上述實(shí)例中通過(guò)生成器完成了調(diào)度,讓兩個(gè)函數(shù)都幾乎同時(shí)執(zhí)行,這樣的調(diào)度不是操作系統(tǒng)進(jìn)行的。而是用戶自己設(shè)計(jì)完成的
這個(gè)程序編寫(xiě)要素:
1 需要使用yield來(lái)讓出控制權(quán)
2 需要循環(huán)幫助執(zhí)行
協(xié)程不是進(jìn)程,也不是線程,它是用戶空間調(diào)度的完成并發(fā)處理的方式。
進(jìn)程,線程由操作系統(tǒng)完成調(diào)度,而協(xié)程是線程內(nèi)完成調(diào)度的,不需要更多的線程,自然也沒(méi)有多線程切換的開(kāi)銷
協(xié)程是非搶占式調(diào)度,只有一個(gè)協(xié)程主動(dòng)讓出控制權(quán),另一個(gè)協(xié)程才會(huì)被調(diào)度。
協(xié)程也不需要使用鎖機(jī)制,因?yàn)槠涫窃谕粋€(gè)線程中執(zhí)行的
多CPU下,可以使用多進(jìn)程和協(xié)程配合,既能進(jìn)程并發(fā),也能發(fā)揮出協(xié)程在單線程中的優(yōu)勢(shì)。
python中的協(xié)程是基于生成器的。
3.4 引入的asyncio,使用裝飾器
#!/usr/bin/poython3.6
#conding:utf-8
import threading
import multiprocessing
import asyncio
@asyncio.coroutine
def a():
for i in range(3):
print (i)
yield
loop=asyncio.get_event_loop()
loop.run_until_complete(a())
loop.close()
結(jié)果如下
#!/usr/bin/poython3.6
#conding:utf-8
import threading
import multiprocessing
import asyncio
@asyncio.coroutine
def a():
for i in range(3):
print (i)
yield
@asyncio.coroutine
def b():
for i in "abc":
print(i)
yield
loop=asyncio.get_event_loop()
task=[a(),b()]
loop.run_until_complete(asyncio.wait(task))
loop.close()
結(jié)果如下
3.5 及其以后版本的書(shū)寫(xiě)方式:
#!/usr/bin/poython3.6
#conding:utf-8
import threading
import multiprocessing
import asyncio
async def a():
for i in range(3):
print (i)
# await asyncio.sleep(0.0001)
async def b(): #使用此方式后,不能再次使用wait了
for i in "abc":
print(i)
# await asyncio.sleep(0.0001)
print (asyncio.iscoroutinefunction(a)) # 此處判斷是否是函數(shù),和調(diào)用無(wú)關(guān)
a=a()
print (asyncio.iscoroutine(a)) # 此處是判斷對(duì)象,是調(diào)用后的結(jié)果
loop=asyncio.get_event_loop()
task=[a,b()]
loop.run_until_complete(asyncio.wait(task))
loop.close()
結(jié)果如下
async def 用來(lái)定義協(xié)程函數(shù),iscoroutinefunction()返回True,協(xié)程函數(shù)中可以不包含await,async關(guān)鍵字,但是不能使用yield關(guān)鍵字
如果生成器函數(shù)調(diào)用返回生成器對(duì)象一樣,協(xié)程函數(shù)調(diào)用也會(huì)返回一個(gè)對(duì)象成為協(xié)程對(duì)象,iscoroutine()返回為T(mén)rue
#!/usr/bin/poython3.6
#conding:utf-8
import threading
import multiprocessing
import asyncio
import socket
ip='192.168.1.200'
port=9999
async def handler(conn,send):
while True:
data=await conn.read(1024) # 接受客戶端的數(shù)據(jù),相當(dāng)于recv,wait 就是IO等待,此處會(huì)等待
print (conn,send)
client_addr=send.get_extra_info('peername') # 獲取客戶端信息
msg="{} {}".format(data.decode(),client_addr).encode() #封裝消息
send.write(msg) # 傳輸?shù)娇蛻舳? await send.drain() # 此處相當(dāng)于makefile中的flush ,此處也會(huì)IO等待
loop=asyncio.get_event_loop() #實(shí)例化一個(gè)循環(huán)事件
crt=asyncio.start_server(handler,ip,port,loop=loop) #使用異步方式啟動(dòng)函數(shù),最后一個(gè)參數(shù)是應(yīng)該用誰(shuí)來(lái)循環(huán)處理
server=loop.run_until_complete(crt) # 此處是直到此方法完成后終止
print (server)
try:
loop.run_forever()
except KeyboardInterrupt:
pass
finally:
server.close()
loop.close()
異步的http 庫(kù),使用協(xié)程實(shí)現(xiàn)的
需要安裝第三方模塊 aiohttp
pip install aiohttp
http server 基礎(chǔ)實(shí)現(xiàn)
#!/usr/bin/poython3.6
#conding:utf-8
from aiohttp import web
async def indexhandle(request:web.Request): # 處理客戶端請(qǐng)求函數(shù)
print("web",web.Request)
return web.Request(text=request.path,status=201) #返回文本和狀態(tài)碼
async def handle(request:web.Request):
print (request.match_info)
print (request.query_string)
return web.Response(text=request.match_info.get('id','0000'),status=200) # 此處是返回給客戶端的數(shù)據(jù),后面的0000是默認(rèn)
app=web.Application()
#路由選路,
app.router.add_get('/',indexhandle) # http://192.168.1.200:80/
app.router.add_get('/{id}',handle) # http://192.168.1.200:80/12345
web.run_app(app,host='0.0.0.0',port=80) #監(jiān)聽(tīng)I(yíng)P和端口并運(yùn)行
客戶端實(shí)現(xiàn)
#!/usr/bin/poython3.6
#conding:utf-8
import asyncio
from aiohttp import ClientSession
async def get_html(url:str):
async with ClientSession() as session: # 獲取session,要和服務(wù)端通信,必須先獲取session,之后才能進(jìn)行相關(guān)的操作 ,此處使用with是打開(kāi)關(guān)閉會(huì)話,保證會(huì)話能夠被關(guān)閉。
async with session.get(url) as res: # 需要這個(gè)URL資源,獲取,
print (res.status) # 此處返回為狀態(tài)碼
print (await res.text()) # 此處返回為文本信息
url='http://www.baidu.com'
loop=asyncio.get_event_loop()
loop.run_until_complete(get_html(url))
loop.close()