python同步編程和異步編程

一 簡介

1 同步和異步

函數(shù)或方法掉調(diào)用的時(shí)候,被調(diào)用者是否能得到最終結(jié)果來判斷同步和異步
直接得到最終結(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ù)

同步就是我讓你打飯,你不打好我就不走開,直到你打飯給了我
異步就是我讓你打飯,你等著,我不等你,但是我會(huì)盯著你,你打完我會(huì)過來拿走,異步并不能保證多長時(shí)間將飯打完。

異步給的是臨時(shí)結(jié)果,目前是拿不到的
同步只看結(jié)果是不是最終結(jié)果進(jìn)行判斷

2 阻塞,非阻塞

函數(shù)或方法調(diào)用的時(shí)候,是否立即返回
立即返回就是非阻塞調(diào)用
不立即返回就是阻塞調(diào)用

3 區(qū)別

同步,異步,阻塞,非阻塞 不相關(guān)
同步異步強(qiáng)調(diào)的是結(jié)果
阻塞,非阻塞強(qiáng)調(diào)的是時(shí)間,是否等待

同步和異步的區(qū)別在于:調(diào)用者是否得到可想要的結(jié)果

同步就是一直要執(zhí)行到返回結(jié)果

異步就是直接返回了,但是不是最終結(jié)果,調(diào)用者不能通過這種調(diào)用方式得到結(jié)果,還是需要通過被調(diào)用者,使用其他方式通知調(diào)用者,來取回最終的結(jié)果

同步阻塞:我啥事也不干,就等你打飯給我,打飯是結(jié)果,而且我啥事也不敢就一直等,同步加阻塞。

同步非阻塞:我等著你打飯給我,但我可以完手機(jī),看電視,打飯是結(jié)果,但我不一直等

異步阻塞: 我要打飯,你說等號(hào),并沒有給我返回飯,我啥事也不干,就等著飯好了叫我,叫號(hào)。

異步非阻塞:我要打飯,你說等號(hào),并沒有返回飯,我在旁邊看電視,玩手機(jī),反打好了叫我。

4 同步IO,異步IO,IO 多路復(fù)用

1 IO 兩個(gè)階段

1 數(shù)據(jù)準(zhǔn)備階段
2 內(nèi)核空間復(fù)制會(huì)用戶進(jìn)程緩沖區(qū)階段

2 發(fā)生IO的時(shí)候

1 內(nèi)核從輸入設(shè)備讀寫數(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

5 零拷貝

1 零拷貝概念

CPU 不執(zhí)行拷貝數(shù)據(jù)從一個(gè)存儲(chǔ)區(qū)域到另一個(gè)存儲(chǔ)區(qū)域的任務(wù),這通常用于通過網(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)資源。通過使CPU得以完成其他而非將機(jī)器中的數(shù)據(jù)復(fù)制到另一處的任務(wù),性能也得到了增強(qiáng)。另外,零復(fù)制操作減少了在用戶空間與內(nèi)核空間之間切換模式的次數(shù)。

零復(fù)制協(xié)議對(duì)于網(wǎng)絡(luò)鏈路容量接近或超過CPU處理能力的高速網(wǎng)絡(luò)尤為重要。在這種網(wǎng)絡(luò)下,CPU幾乎將所有時(shí)間都花在復(fù)制要傳送的數(shù)據(jù)上,因此將成為使通信速率低于鏈路容量的瓶頸。

2 零拷貝帶來的好處

1 減少甚至完全避免不必要的CPU拷貝,從而讓CPU 解脫出來去執(zhí)行其他任務(wù)
2 減少內(nèi)存帶寬占用
3 通常零拷貝技術(shù)還能減少用戶空間和內(nèi)核空間之間的上下文切換

3 Linux 系統(tǒng)的"用戶空間"和"內(nèi)核空間"

從Linux系統(tǒng)來看,除了引導(dǎo)系統(tǒng)的BIN區(qū),整個(gè)內(nèi)存空間主要被分成兩部分:

1 內(nèi)核空間(kernel space ) : 主要提供給程序調(diào)度,內(nèi)存分配,連接硬件資源等程序邏輯空間

2 用戶空間 (user space): 提供給各個(gè)進(jìn)程的主要空間,用戶空間不具備訪問內(nèi)核空間資源的權(quán)限,因此如果應(yīng)用程序需要使用到內(nèi)核空間的資源,則需要通過系統(tǒng)調(diào)度來完成,從用戶空間切換到內(nèi)核空間,然后在完成操作后再從內(nèi)核空間切換到用戶空間

4 Linux 中的零拷貝技術(shù)的實(shí)現(xiàn)方向

1 直接I/O: 對(duì)于這種傳輸方式來說,應(yīng)用程序可以直接訪問硬件存儲(chǔ),操作系統(tǒng)內(nèi)核只是輔助數(shù)據(jù)傳輸,這種方式依舊存在用戶空間和內(nèi)核空間的上下文切換,但硬件上的數(shù)據(jù)不會(huì)拷貝到內(nèi)核空間,而是直接拷貝到可用戶空間,因此直接IO不存在內(nèi)核空間緩沖區(qū)和用戶空間緩沖區(qū)之間的數(shù)據(jù)拷貝


2 在數(shù)據(jù)傳輸過程中,避免數(shù)據(jù)在用戶空間緩沖區(qū)和內(nèi)核空間緩沖區(qū)之間的CPU拷貝,以及數(shù)據(jù)在系統(tǒng)內(nèi)核空間的CPU拷貝,


3 copy-on-write(寫時(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)的支持。

5 傳統(tǒng)I/O 操作

1 發(fā)起read系統(tǒng)調(diào)用: 導(dǎo)致用戶空間到內(nèi)核空間的上下文切換(第一次上下文切換),通過DMA引擎將文件中的數(shù)據(jù)從磁盤上讀取到內(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)核空間到用戶空間的再次上下文切換(第四次上下文切換),通過DMA引擎將內(nèi)核緩沖區(qū)中的數(shù)據(jù)傳遞到協(xié)議引擎(第四次拷貝:socket buffer -> protocol engine ),這次拷貝時(shí)獨(dú)立的異步的過程。


事實(shí)上調(diào)用的返回并不保證數(shù)據(jù)被傳輸,甚至不保證數(shù)據(jù)傳輸?shù)拈_始,只是意味著將我么要發(fā)送的數(shù)據(jù)放入到了一個(gè)待發(fā)送的隊(duì)列中,除非實(shí)現(xiàn)了優(yōu)先環(huán)或者隊(duì)列,否則會(huì)是先進(jìn)先出的方式發(fā)送數(shù)據(jù)的。

總的來說,傳統(tǒng)的I/O操作進(jìn)行了4次用戶空間與內(nèi)核空間的上下文切換,以及4次數(shù)據(jù)拷貝。其中4次數(shù)據(jù)拷貝中包括了2次DMA拷貝和2次CPU拷貝。

傳統(tǒng)模式為何將數(shù)據(jù)從磁盤讀取到內(nèi)核空間而不是直接讀取到用戶空間緩沖區(qū),其原因是為了減少IO操作以提高性能,因?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ū)即可,無需再進(jìn)行一次低效的磁盤IO操作。

Bufferedinputstream 作用是會(huì)根據(jù)情況自動(dòng)為我們預(yù)讀取更多的數(shù)據(jù)到他自己維護(hù)的一個(gè)內(nèi)部字節(jié)數(shù)據(jù)緩沖區(qū),這樣能減少系統(tǒng)調(diào)用次數(shù)來提高性能。

總的來說,內(nèi)核緩沖區(qū)的一大作用是為了減少磁盤IO操做,Bufferedinputstream 則是減少"系統(tǒng)調(diào)用"

6 DMA

DMA(direct memory access) --- 直接內(nèi)存訪問,DMA 是允許外設(shè)組件將IO數(shù)據(jù)直接傳送到主存儲(chǔ)器并并且傳輸不需要CPU參與,以此解放CPU去做其他的事情。
而用戶空間與內(nèi)核空間之間的數(shù)據(jù)傳輸并沒有類似DMA這種可以不需要CPU參與的傳輸工具,因此用戶空間與內(nèi)核空間之間的數(shù)據(jù)傳輸是需要CPU全程參與的。所有也就有了通過零拷貝技術(shù)來減少和避免不必要的CPU數(shù)據(jù)拷貝過程。

7 通過sendfile 實(shí)現(xiàn)零拷貝IO

1 發(fā)起sendfile系統(tǒng)調(diào)用,導(dǎo)致用戶空間到內(nèi)核空間的上下文切換(第一次上下文切換),通過DMA引擎將磁盤文件中的內(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)核空間到用戶空間的上下文切換(第二次上下文切換)。通過DMA 引擎將內(nèi)核空間的socket緩沖區(qū)的數(shù)據(jù)傳遞到協(xié)議引擎(第三次拷貝:socket buffer-> protocol engine )


總的來說,通過sendfile實(shí)現(xiàn)的零拷貝I/O只使用了2次用戶空間與內(nèi)核空間的上下文切換,以及3次數(shù)據(jù)的拷貝。其中3次數(shù)據(jù)拷貝中包括了2次DMA拷貝和1次CPU拷貝。

Q:但通過是這里還是存在著一次CPU拷貝操作,即,kernel buffer ——> socket buffer。是否有辦法將該拷貝操作也取消掉了?
A:有的。但這需要底層操作系統(tǒng)的支持。從Linux 2.4版本開始,操作系統(tǒng)底層提供了scatter/gather這種DMA的方式來從內(nèi)核空間緩沖區(qū)中將數(shù)據(jù)直接讀取到協(xié)議引擎中,而無需將內(nèi)核空間緩沖區(qū)中的數(shù)據(jù)再拷貝一份到內(nèi)核空間socket相關(guān)聯(lián)的緩沖區(qū)中。

8 帶有DMA 收集拷貝功能的sendfile 實(shí)現(xiàn)的IO

從Linux 2.4 開始,操做系統(tǒng)底層提供了帶有scatter/gather 的DMA來從內(nèi)核空間緩沖區(qū)中將數(shù)據(jù)讀取到協(xié)議引擎中,這樣以來待傳輸?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)核空間的上下文切換,通過DMA 引擎將磁盤文件內(nèi)容拷貝到內(nèi)核空間緩沖區(qū)中(第一次拷貝: hard drive -> kernel buffer)


2 沒有數(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ù)拷貝


總的來說,帶有DMA收集拷貝功能的sendfile實(shí)現(xiàn)的I/O只使用了2次用戶空間與內(nèi)核空間的上下文切換,以及2次數(shù)據(jù)的拷貝,而且這2次的數(shù)據(jù)拷貝都是非CPU拷貝。這樣一來我們就實(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版本開始,out_fd 可以是任意類型文件描述符,所以從Linux 2.6.33 版本開始sendfile 可以支持文件到文件,文件到套接字之間的數(shù)據(jù)傳輸。

9 傳統(tǒng)I/O 和零拷貝及sendfile零拷貝I/O比較

傳統(tǒng)I/O通過兩條系統(tǒng)指令read、write來完成數(shù)據(jù)的讀取和傳輸操作,以至于產(chǎn)生了4次用戶空間與內(nèi)核空間的上下文切換的開銷;而sendfile只使用了一條指令就完成了數(shù)據(jù)的讀寫操作,所以只產(chǎn)生了2次用戶空間與內(nèi)核空間的上下文切換。
傳統(tǒng)I/O產(chǎn)生了2次無用的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ù)拷貝過程,因此sendfile零拷貝I/O的實(shí)現(xiàn)是完成在內(nèi)核空間中完成的,這對(duì)于應(yīng)用程序來說就無法對(duì)數(shù)據(jù)進(jìn)行操作了。
Q:對(duì)于上面的第三點(diǎn),如果我們需要對(duì)數(shù)據(jù)進(jìn)行操作該怎么辦了?
A:Linux提供了mmap零拷貝來實(shí)現(xiàn)我們的需求

10 通過mmap 實(shí)現(xiàn)零拷貝I/O

Mmap(內(nèi)存映射)是一個(gè)比sendfile昂貴但優(yōu)于傳統(tǒng)IO的方式

1 發(fā)出mmap系統(tǒng)調(diào)用,導(dǎo)致用戶空間到內(nèi)核空間的上下文切換(第一次上下文切換)。通過DMA引擎將磁盤文件中的內(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)核空間到用戶空間的上下文切換(第四次上下文切換),通過DMA 引擎將內(nèi)核空間socket緩沖區(qū)的數(shù)據(jù)傳遞到協(xié)議引擎(第三次拷貝: socket buffer---> protocol engine)

總的來說,通過mmap實(shí)現(xiàn)的零拷貝I/O進(jìn)行了4次用戶空間與內(nèi)核空間的上下文切換,以及3次數(shù)據(jù)拷貝。其中3次數(shù)據(jù)拷貝中包括了2次DMA拷貝和1次CPU拷貝。

6 同步IO

1 同步阻塞IO

python同步編程和異步編程

在文件讀取進(jìn)入內(nèi)核空間和從內(nèi)核空間拷貝進(jìn)入用戶進(jìn)程空間的過程中,沒有任何的數(shù)據(jù)返回,客戶端在一直等待狀態(tài)。

2 同步非阻塞

python同步編程和異步編程

進(jìn)程調(diào)用read操作,如果IO沒有準(zhǔn)備好,立即返回ERROR,進(jìn)程不阻塞,用戶可以再次發(fā)起系統(tǒng)調(diào)用,如果內(nèi)核已經(jīng)準(zhǔn)備好,就阻塞,然后復(fù)制數(shù)據(jù)到用戶空間

第一階段數(shù)據(jù)沒準(zhǔn)備好,就先忙別的,等會(huì)再看看,檢查數(shù)據(jù)是否準(zhǔn)備好了的過程是非阻塞的

第二階段是阻塞的,及內(nèi)核空間和用戶空間之間復(fù)制數(shù)據(jù)是阻塞的,但是要等待飯盛好才是完事,這是同步的。

3 IO 多路復(fù)用

python同步編程和異步編程

所謂的IO多路復(fù)用,就是同時(shí)監(jiān)控多個(gè)IO,有一個(gè)準(zhǔn)備好了,就不需要等待開始處理,提高了同時(shí)處理IO的能力

select是所有平臺(tái)都支持,poll是對(duì)select的升級(jí)

epoll,Linux 系統(tǒng)內(nèi)核2.5+ 開始支持,對(duì)select和epoll的增強(qiáng),在監(jiān)視的基礎(chǔ)上,增加了回調(diào)機(jī)制,BSD,Mac的kqueue,還有windows的iocp

如果既想訪問網(wǎng)絡(luò),又想訪問文件,則先將準(zhǔn)備好的數(shù)據(jù)先處理,那個(gè)準(zhǔn)備好了就處理那個(gè)

能夠提高同時(shí)處理IO的能力,誰先做玩我先處理誰

上面的兩種方式,效率太差了,等完一個(gè)完成后再等一個(gè),太慢了。

誰好了處理誰,不同的平臺(tái)對(duì)IO多路復(fù)用的實(shí)現(xiàn)方式是不同的

Select 和 poll 在Linux,Windows,和MAC中都支持

一般來將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)備好的通知是沒有針對(duì)性的,需要用戶自己找到是否是自己的并進(jìn)行處理。select做到的是時(shí)間重疊

epoll增加了回調(diào)機(jī)制,那一路準(zhǔn)備好了,我會(huì)告訴你,有一種是你不用管了,好了我直接替你調(diào)用。

7 異步調(diào)用

python同步編程和異步編程

兩個(gè)階段
等待數(shù)據(jù)準(zhǔn)備和拷貝階段
立即返回?cái)?shù)據(jù),給一個(gè)號(hào)。到時(shí)候叫號(hào),直接返回
信號(hào)句柄,告訴你幾號(hào)好了,(signal handler process datagram)
有些時(shí)候是需要爭搶的
我可以不通知你,我也可以通知你后你再來
理解數(shù)據(jù)層面的東西,就不要理解其他的socket層面的東西
文件中實(shí)際就是兩個(gè)緩沖隊(duì)列,每個(gè)隊(duì)列是一個(gè)。
在異步模型中,操作系統(tǒng)通你的,你是在用戶空間的,操作系統(tǒng)可以是在內(nèi)核空間的,進(jìn)程和線程等等的都是操作系統(tǒng)層面的東西。
整個(gè)過程中進(jìn)程都可以做其他的事,就算是通知了,也不一定要立即反應(yīng),這和你的設(shè)置有關(guān)
Linux中的AIO 的系統(tǒng)調(diào)用,內(nèi)核版本從2.6開始支持
一般的IO是IO多路復(fù)用和異步復(fù)用

二 python中的IO 多路復(fù)用

1 簡介

IO 多路復(fù)用
大多數(shù)操作系統(tǒng)都支持select和poll
Linux 2.5+ 支持epoll
BSD,Mac支持kqueue
Windows 的 iocp
python的select庫

實(shí)現(xiàn)了select,poll系統(tǒng)調(diào)用,這個(gè)基本上操作系統(tǒng)都支持,部分實(shí)現(xiàn)了epoll,底層的IO多路復(fù)用模塊

開發(fā)中的選擇

1 完全跨平臺(tái),select 和poll ,但其性能較差
2 針對(duì)不同的操作系統(tǒng)自行選擇支持技術(shù),這樣會(huì)提高IO處理能力

selectors庫
3.4 版本后提供這個(gè)庫,高級(jí)的IO復(fù)用庫
類層次結(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)kqueue

selectors.DefaultSelector返回當(dāng)前平臺(tái)最有效,性能最最高的實(shí)現(xiàn)
但是由于沒有實(shí)現(xiàn)windows的IOCP,所以只能退化為select。
默認(rèn)會(huì)自適應(yīng),其會(huì)選擇最佳的方式,Linux 會(huì)直接選擇 epoll ,通過此處,能拿到平臺(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

2 基本方法

abstractmethod register(fileobj,events,data=None)

為selection注冊一個(gè)文件獨(dú)享,監(jiān)視它的IO事件
fileobj 被監(jiān)視的文件對(duì)象,如socket對(duì)象
events 事件,該文件對(duì)象必須等待的事件,read或write

python同步編程和異步編程

data 可選的與此文件對(duì)象相關(guān)的不透明數(shù)據(jù),如可用來存儲(chǔ)每個(gè)客戶端的會(huì)話ID,可以是函數(shù),類,實(shí)例,如果是函數(shù),有點(diǎn)回調(diào)的意思,通知某個(gè)函數(shù),某個(gè)實(shí)例,某個(gè)類,可以是類屬性,等,都可以,None表示消息發(fā)生了,沒人認(rèn)領(lǐng)。

3 基本實(shí)現(xiàn)socket 操作監(jiān)控

1 思路

第一步 :需要實(shí)例化 ,選擇一個(gè)最優(yōu)的實(shí)現(xiàn),將其實(shí)例化(選擇不同平臺(tái)實(shí)現(xiàn)的IO復(fù)用的最佳框架),python內(nèi)部處理


第二步:注冊函數(shù),將要監(jiān)控對(duì)象,要監(jiān)控事件和監(jiān)控觸發(fā)后對(duì)象寫入register注冊中

1 注冊: 對(duì)象,啥事件,調(diào)用的函數(shù)
2 進(jìn)行循環(huán)和監(jiān)控select函數(shù)的返回,當(dāng)監(jiān)控的對(duì)象的事件滿足時(shí)會(huì)立即返回,在events中可以拿到這些數(shù)據(jù)events中有我是誰,我是什么事件觸發(fā)的(讀和寫),讀的滿足可以recv,key 是讓我監(jiān)控的東西,event是其什么事件觸發(fā)的。將對(duì)象和事件拿到后做相應(yīng)的處理。


第三步:實(shí)時(shí)關(guān)注socket有讀寫操作,從而影響events的變化

對(duì)socket來判斷有沒有讀,若讀了,則直接觸發(fā)對(duì)應(yīng)的機(jī)制進(jìn)行處理。一旦有新的連接準(zhǔn)備,則會(huì)將其消息發(fā)送給對(duì)應(yīng)的函數(shù)進(jìn)行處理相關(guān)的操作。被調(diào)用的函數(shù)是有要求的,其需要傳送mask的,data 就是未來要調(diào)用的函數(shù),建立了事件和未來參數(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)行處理

在注冊的時(shí)候,后面加了data,后面直接使用,直接調(diào)用,不用管其他,data和每一個(gè)觀察者直接對(duì)應(yīng)起來的。

只要有一個(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是注冊的唯一的東西,socket 也可以,但是可以定義socket的讀和寫,一般都是合著的


第四步:調(diào)用對(duì)應(yīng)事件的對(duì)象,并執(zhí)行相關(guān)操作

然后將events拿出來解構(gòu),key本身是一個(gè)多元祖,key上保存著注冊塞進(jìn)去的data,key是存儲(chǔ)了4個(gè)信息的元祖,此處的data稱為回調(diào)函數(shù),加上() 稱為調(diào)用

2 代碼實(shí)現(xiàn)

python同步編程和異步編程
python同步編程和異步編程

代碼下載目錄
IO 多路復(fù)用初始代碼

https://pan.baidu.com/s/18B5OL89Z4YSxEmX4gNkgDA

3 基本參數(shù)講解

1 events參數(shù):

2019-09-01 09:37:46 Thread-1 events: [(SelectorKey(fileobj=<socket.socket fd=4, family=AddressFamily.AF_INET, type=2049, proto=0, laddr=('192.168.1.200', 9999)>, fd=4, events=1, data=<function accept at 0x7f50feb61d90>), 1)]

events中包含了兩組
第一組 :
fileobj 及套接字返回的相關(guān)參數(shù),和之前的socket中的accpet中的conn 相似,

fd 及文件描述符

events 及事件類型,python同步編程和異步編程兩種

data 及注冊調(diào)用的函數(shù),上述的有accept 和recv 函數(shù)


第二組:
1 events 的狀態(tài),及mask

2 select.get_map() 參數(shù):

1 select.get_map().items() 中的key
2019-09-01 09:43:52 MainThread key:SelectorKey(fileobj=<socket.socket fd=4, family=AddressFamily.AF_INET, type=2049, proto=0, laddr=('192.168.1.200', 9999)>, fd=4, events=1, data=<function accept at 0x7fcf5a50ad90>)

此處的key和上面的列表中的二元祖中的前一個(gè)完全相同


2 select.get_map().items() 中的fobj
2019-09-01 09:43:52 MainThread fobj: 4
其是其中的文件描述符

4 總結(jié):

IO 多路復(fù)用就是一個(gè)線程來處理所有的IO
在單線程中進(jìn)行處理IO多路復(fù)用
多線程中的IO阻塞時(shí)浪費(fèi)CPU資源,其是等待狀態(tài),等待狀態(tài)雖然不占用CPU資源,但線程本身的狀態(tài)需要維持,還是會(huì)占用一定的資源

4 改進(jìn)版本的socket 監(jiān)控

1 描述

send 是寫操作,有可能阻塞,也可以監(jiān)聽
recv所在的注冊函數(shù),要監(jiān)聽python同步編程和異步編程讀與寫事件,回調(diào)的時(shí)候,需要mask 來判斷究竟是讀觸發(fā)了還是寫觸發(fā)了,所以,需要修改方法聲明,增加mask
寫操作當(dāng)發(fā)送群聊時(shí),其每個(gè)鏈接是獨(dú)立的,需要queue隊(duì)列保存相關(guān)的數(shù)據(jù),并進(jìn)行接受和發(fā)送操作

2 代碼實(shí)現(xiàn)

python同步編程和異步編程
python同步編程和異步編程

python同步編程和異步編程

IO 多路復(fù)用最終代碼

https://pan.baidu.com/s/1y-3j607_5DxBpa4wZNxCEQ

三 異步編程

1 asyncio 簡介

3.4 版本加入標(biāo)準(zhǔn)庫
asyncio 底層是基于selectors實(shí)現(xiàn)的,看似庫,其實(shí)就是一個(gè)框架,包括異步IO,事件循環(huán),協(xié)程,任務(wù)等


并行和串行的區(qū)分:
兩個(gè)事件的因果關(guān)系:
若有因果關(guān)系,則可以使用串行
若無因果關(guān)系,則可以使用并行,及多線程來處理

2 相關(guān)參數(shù)及詳解

參數(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)閉事件

3 協(xié)程

1 基本實(shí)例

#!/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()

python同步編程和異步編程

此處的默認(rèn)執(zhí)行順序是a()到b()的順序執(zhí)行,若要使其交叉執(zhí)行,則需要使用yield 來實(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)

python同步編程和異步編程

上述實(shí)例中通過生成器完成了調(diào)度,讓兩個(gè)函數(shù)都幾乎同時(shí)執(zhí)行,這樣的調(diào)度不是操作系統(tǒng)進(jìn)行的。而是用戶自己設(shè)計(jì)完成的


這個(gè)程序編寫要素:
1 需要使用yield來讓出控制權(quán)
2 需要循環(huán)幫助執(zhí)行

2 協(xié)程簡介

協(xié)程不是進(jìn)程,也不是線程,它是用戶空間調(diào)度的完成并發(fā)處理的方式。
進(jìn)程,線程由操作系統(tǒng)完成調(diào)度,而協(xié)程是線程內(nèi)完成調(diào)度的,不需要更多的線程,自然也沒有多線程切換的開銷
協(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)勢。
python中的協(xié)程是基于生成器的。

3 協(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é)果如下

python同步編程和異步編程

#!/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é)果如下

python同步編程和異步編程

3.5 及其以后版本的書寫方式:

#!/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)用無關(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é)果如下

python同步編程和異步編程

async def 用來定義協(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()返回為True

4 TCP echo server 實(shí)現(xiàn)

#!/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)該用誰來循環(huán)處理
server=loop.run_until_complete(crt) # 此處是直到此方法完成后終止

print (server)
try:
    loop.run_forever()
except KeyboardInterrupt:
    pass
finally:
    server.close()
    loop.close()

5 擴(kuò)展aiohttp 庫

異步的http 庫,使用協(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):  # 處理客戶端請求函數(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)聽IP和端口并運(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是打開關(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()

當(dāng)前標(biāo)題:python同步編程和異步編程
分享鏈接:http://muchs.cn/article30/gdegso.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供品牌網(wǎng)站建設(shè)、全網(wǎng)營銷推廣電子商務(wù)、網(wǎng)站營銷品牌網(wǎng)站制作、建站公司

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

手機(jī)網(wǎng)站建設(shè)