欧美三区_成人在线免费观看视频_欧美极品少妇xxxxⅹ免费视频_a级毛片免费播放_鲁一鲁中文字幕久久_亚洲一级特黄

Python3 協(xié)程

系統(tǒng) 1676 0

協(xié)程

定義: 協(xié)程,又稱微線程,纖程。英文名Coroutine。一句話說明什么是線程: 協(xié)程是一種用戶態(tài)的輕量級線程 。協(xié)程的標(biāo)準(zhǔn)定義:

  • ? ? 必須在只有一個單線程里實(shí)現(xiàn)并發(fā)
  • ? ? 修改共享數(shù)據(jù)不需加鎖
  • ? ? 用戶程序里自己保存多個控制流的上下文棧
  • ? ? 一個協(xié)程遇到IO操作自動切換到其它協(xié)程

特點(diǎn): 協(xié)程擁有自己的寄存器上下文和棧。協(xié)程調(diào)度切換時,將寄存器上下文和棧保存到其他地方,在切回來的時候,恢復(fù)先前保存的寄存器上下文和棧。因此,協(xié)程能保留上一次調(diào)用時的狀態(tài)(即所有局部狀態(tài)的一個特定組合),每次過程重入時,就相當(dāng)于進(jìn)入上一次調(diào)用的狀態(tài),換種說法:進(jìn)入上一次離開時所處邏輯流的位置。 線程是CPU控制的,而協(xié)程是程序自身控制的

優(yōu)點(diǎn):

? ? ? ? ? ? ????????????????1、由于自身帶有上下文和棧,無需線程上下文切換的開銷;

? ? ? ? ? ? ? 2、無需原子操作的鎖定及同步的開銷;

? ? ? ? ? ? ? 3、方便切換控制流,簡化編程模型

? ? ? ? ? ? ? 4、可以輕松實(shí)現(xiàn)高并發(fā),且可擴(kuò)展性高,成本低

缺點(diǎn):

? ? ? ? ? ? ? 1、無法利用多核資源:協(xié)程的本質(zhì)是個單線程,它不能同時將 單個CPU 的多個核用上,協(xié)程需要和進(jìn)程配合才能運(yùn)行在多CPU上.當(dāng)然我們?nèi)粘K帉懙慕^大部分應(yīng)用都沒有這個必要,除非是cpu密集型應(yīng)用。

? ? ? ? ? ? ? 2、當(dāng)進(jìn)行阻塞(Blocking)操作時,會阻塞整個程序。

Greenlet模塊

greenlet是一個用C實(shí)現(xiàn)的協(xié)程模塊,通過設(shè)置 .switch() 可以實(shí)現(xiàn)任意函數(shù)之間的切換,但這種切換屬于 手動切換,當(dāng)遇到IO操作時,程序會阻塞,而不能自動進(jìn)行切換 。舉例如下:

            
              from greenlet import greenlet
import time
def test1():
    print(" running test1")
    gr2.switch() # 切換到test2
    print(" running test1 again ")
    time.sleep(2)
    gr2.switch()

def test2():
    print("\033[31;1m running test2 \033[0m")
    gr1.switch()
    print("\033[32;1m running test2 again\033[0m")

gr1 = greenlet(test1) # 實(shí)例化一個協(xié)程
gr2 = greenlet(test2) # 實(shí)例化另一個協(xié)程
gr1.switch() # 執(zhí)行g(shù)r1,切換到grl1執(zhí)行test1
運(yùn)行結(jié)果:
 running test1
 running test2 
 running test1 again 
 running test2 again
            
          

Gevent模塊

Gevent 是一個第三方庫,可以輕松通過gevent實(shí)現(xiàn)并發(fā)同步或異步編程,在gevent中用到的主要模式是 Greenlet , 它是以C擴(kuò)展模塊形式接入Python的輕量級協(xié)程。 Greenlet全部運(yùn)行在主程序操作系統(tǒng)進(jìn)程的內(nèi)部,但它們被協(xié)作式地調(diào)度。Gevnet遇到IO操作時,會進(jìn)行自動切話,屬于主動式切換。例如:

            
              import gevent,time
def func1():
    print(' 主人來電話啦...')
    gevent.sleep(3)
    print(' 主人那家伙又來電話啦...')

def func2():
    print('\033[32;1m 打個電話...\033[0m')
    gevent.sleep(2)
    print('\033[32;1m 咦,電話給掛了,接著打...\033[0m')

def func3():
    print("\033[31;1m 哈哈哈哈 \033[0m")
    gevent.sleep(0)
    print("\033[31;1m 嘿嘿嘿。。。。\033[0m")

start_time = time.time()
gevent.joinall([
    gevent.spawn(func2), # 生成一個協(xié)程
    gevent.spawn(func1),
    gevent.spawn(func3),
])
print("\033[32;1m running time:", (time.time() - start_time))
運(yùn)行結(jié)果:
 打個電話...
 主人來電話啦...
 哈哈哈哈 
 嘿嘿嘿。。。。
 咦,電話給掛了,接著打...
 主人那家伙又來電話啦...
 running time: 3.006500244140625
# 可以看出協(xié)程并發(fā)的效果,遇到IO操作時會自動進(jìn)行切換,當(dāng)IO操作沒有完成時,會不停的進(jìn)行循環(huán)切換,看哪個IO操作完成了。
            
          

事件驅(qū)動模型

將所有發(fā)生的事件按照先后順序存放在事件隊(duì)列中(先進(jìn)先出,與隊(duì)列相似),?事件(消息)一般都各自保存各自的處理函數(shù)指針,這樣每一種事件對應(yīng)相應(yīng)的處理函數(shù),再循環(huán)事件隊(duì)列進(jìn)行處理。

為什么用到事件驅(qū)動模型?

? ? ? ?通常在寫服務(wù)器處理模型的程序時,對于接收到請求,有三種常用的處理模型: 新建一個進(jìn)程、新建一個線程或者事件驅(qū)動模型 。新建一個進(jìn)程實(shí)現(xiàn)比較簡單,但當(dāng)請求較多時,會導(dǎo)致服務(wù)器的開銷變大,從而降低服務(wù)器的性能;新建一個線程,會涉及到線程的同步,可能會面臨死鎖的問題;事件驅(qū)動模型,可以很好地解決前兩個問題,但實(shí)現(xiàn)邏輯比較復(fù)雜。
? ? ? ? ? ?目前大部分的UI編程都是事件驅(qū)動模型,如很多UI平臺都會提供onClick()事件,這個事件就代表鼠標(biāo)按下事件。事件驅(qū)動模型大體思路如下:

  1. 有一個事件(消息)隊(duì)列;
  2. 鼠標(biāo)按下時,往這個隊(duì)列中增加一個點(diǎn)擊事件(消息);
  3. 有個循環(huán),不斷從隊(duì)列取出事件,根據(jù)不同的事件,調(diào)用不同的函數(shù),如onClick()、onKeyDown()(按下鍵盤)等;
  4. 事件(消息)一般都各自保存各自的處理函數(shù)指針,這樣,每個消息都有獨(dú)立的處理函數(shù);

Python3 協(xié)程_第1張圖片
? ? ? ? 圖中事件1假如是鼠標(biāo)點(diǎn)擊,那么鼠標(biāo)點(diǎn)擊一下就將該事件放入這個事件隊(duì)列中;假如事件2是按下鍵盤事件,也將該事件放入事件隊(duì)列中;?線程會循環(huán)的去處理事件隊(duì)列中的事件。?將事件加入到事件列表,和提取事件處理相互是不影響的,事件的處理速度,并不影響事件的產(chǎn)生速度,這就是典型的生產(chǎn)者消費(fèi)者模型。比如我每秒點(diǎn)10次鼠標(biāo),但是你的處理速度是每秒8次,雖然你處理的慢,但是并不影響我繼續(xù)點(diǎn)擊鼠標(biāo)。事件驅(qū)動模型就是根據(jù)事件做出相應(yīng)的反應(yīng),比如點(diǎn)下文檔的'X'就關(guān)閉文檔,點(diǎn)擊 '-' 就最小化文檔。

常見的IO操作

  • CPU

中央處理器(CPU,Central Processing Unit)是一塊超大規(guī)模的集成電路,是一臺計(jì)算機(jī)的運(yùn)算核心(Core)和控制核心( Control Unit)。它的功能主要是解釋計(jì)算機(jī)指令以及處理計(jì)算機(jī)軟件中的數(shù)據(jù)。物理結(jié)構(gòu)主要包括:
?? 邏輯部件:運(yùn)算邏輯部件。主要是進(jìn)行定點(diǎn)或浮點(diǎn)算數(shù)運(yùn)算操作、移位操作以及邏輯操作,還可以進(jìn)行地址運(yùn)算和轉(zhuǎn)換;
?? 寄存器部件:保存指令執(zhí)行過程中臨時存放的寄存器操作數(shù)和中間(或最終)的操作結(jié)果;
?? 控制部件:主要是負(fù)責(zé)對指令譯碼,并且發(fā)出為完成每條指令所要執(zhí)行的各個操作的控制信號;
?? CPU具有以下4個方面的基本功能:數(shù)據(jù)通信,資源共享,分布式處理,提供系統(tǒng)可靠性。運(yùn)作原理可基本分為四個階段:提?。‵etch)、解碼(Decode)、執(zhí)行(Execute)和寫回(Writeback)。

  • 用戶空間與內(nèi)核空間

現(xiàn)在操作系統(tǒng)都是采用虛擬存儲器,那么對32位操作系統(tǒng)而言,它的尋址空間(虛擬存儲空間)為4G(2的32次方)。操作系統(tǒng)的核心是內(nèi)核(操作系統(tǒng)需要使用部分內(nèi)存空間來運(yùn)行,這就是內(nèi)核空間),獨(dú)立于普通的應(yīng)用程序,可以訪問受保護(hù)的內(nèi)存空間,也有訪問底層硬件設(shè)備的所有權(quán)限(比如訪問網(wǎng)卡、音響聲卡都是通過內(nèi)核訪問的,而不是用戶程序)。為了保證用戶進(jìn)程不能直接操作內(nèi)核(kernel),保證內(nèi)核的安全,操心系統(tǒng)將虛擬空間劃分為兩部分,一部分為內(nèi)核空間,一部分為用戶空間。針對linux操作系統(tǒng)而言,將最高的1G字節(jié)(從虛擬地址0xC0000000到0xFFFFFFFF),供內(nèi)核使用,稱為內(nèi)核空間,而將較低的3G字節(jié)(從虛擬地址0x00000000到0xBFFFFFFF),供各個進(jìn)程使用,稱為用戶空間。

  • 進(jìn)程切換
    進(jìn)程切換就是上下文的切換

  • 進(jìn)程阻塞
    正在執(zhí)行的進(jìn)程,由于期待的某些事件未發(fā)生,如請求系統(tǒng)資源失敗、等待某種操作的完成、新數(shù)據(jù)尚未到達(dá)或無新工作做等,則由系統(tǒng)自動執(zhí)行阻塞原語(Block),使自己由運(yùn)行狀態(tài)變?yōu)樽枞麪顟B(tài)(比如socket server等不到client的數(shù)據(jù)就會阻塞)??梢姡M(jìn)程的阻塞是進(jìn)程自身的一種主動行為,也因此只有處于運(yùn)行態(tài)的進(jìn)程(獲得CPU),才可能將其轉(zhuǎn)為阻塞狀態(tài)。當(dāng)進(jìn)程進(jìn)入阻塞狀態(tài),是不占用CPU資源的。

  • 文件描述符id

文件描述符(File descriptor)是計(jì)算機(jī)科學(xué)中的一個術(shù)語,是一個用于表述指向文件的引用的抽象化概念。

文件描述符在形式上是一個非負(fù)整數(shù)。實(shí)際上,它是一個索引值,指向內(nèi)核為每一個進(jìn)程所維護(hù)的該進(jìn)程打開文件的記錄表。當(dāng)程序打開一個現(xiàn)有文件或者創(chuàng)建一個新文件時,內(nèi)核向進(jìn)程返回一個文件描述符。在程序設(shè)計(jì)中,一些涉及底層的程序編寫往往會圍繞著文件描述符展開。但是文件描述符這一概念往往只適用于UNIX、Linux這樣的操作系統(tǒng)。

文件描述符相當(dāng)于一個索引,通過索引打開真正的內(nèi)容。

  • 緩存I/O

緩存 I/O 又被稱作標(biāo)準(zhǔn) I/O,大多數(shù)文件系統(tǒng)的默認(rèn) I/O 操作都是緩存 I/O。在 Linux 的緩存 I/O 機(jī)制中,操作系統(tǒng)會將 I/O 的數(shù)據(jù)緩存在文件系統(tǒng)的頁緩存( page cache )中,也就是說,數(shù)據(jù)會先被拷貝到操作系統(tǒng)內(nèi)核的緩沖區(qū)中,然后才會從操作系統(tǒng)內(nèi)核的緩沖區(qū)拷貝到應(yīng)用程序的地址空間。

緩存 I/O 的缺點(diǎn):
? ? ? ? ? ? 數(shù)據(jù)在傳輸過程中需要在應(yīng)用程序地址空間和內(nèi)核進(jìn)行多次數(shù)據(jù)拷貝操作,這些數(shù)據(jù)拷貝操作所帶來的 CPU 以及內(nèi)存開銷是非常大的。

打開一個文件默認(rèn)不是在用戶的內(nèi)存空間,而是放入了內(nèi)核的緩存中,然后在從內(nèi)核的緩存拷貝到用戶的內(nèi)存空間; 傳數(shù)據(jù)也是一樣,先是到內(nèi)核緩存中,然后才會拷貝到用戶的內(nèi)存空間; 使用內(nèi)核是很耗CPU的,耗CPU是指拷貝到內(nèi)存的這個指令,如果有大量數(shù)據(jù)需要從內(nèi)核緩存拷貝到用戶內(nèi)存空間,那么就會有大量的指令會消耗CPU資源 。訪問網(wǎng)卡、聲卡等只能通過內(nèi)核實(shí)現(xiàn),而用戶空間是無法直接訪問內(nèi)核空間的,所以需要通過內(nèi)核緩存的空間將內(nèi)容拷貝到用戶的內(nèi)存空間,然后用戶才可以使用。

IO模式

剛才說了,對于一次IO訪問(以read舉例),數(shù)據(jù)會先被拷貝到操作系統(tǒng)內(nèi)核的緩沖區(qū)中,然后才會從操作系統(tǒng)內(nèi)核的緩沖區(qū)拷貝到應(yīng)用程序的地址空間。所以說,當(dāng)一個read操作發(fā)生時,它會經(jīng)歷兩個階段:

  1. 等待數(shù)據(jù)準(zhǔn)備 (Waiting for the data to be ready)(就是將數(shù)據(jù)放到內(nèi)核緩存中)
  2. 將數(shù)據(jù)從內(nèi)核拷貝到進(jìn)程中 (Copying the data from the kernel to the process)

正是因?yàn)檫@兩個階段,linux系統(tǒng)產(chǎn)生了下面五種網(wǎng)絡(luò)模式的方案。

  • 阻塞 I/O(blocking IO)
  • 非阻塞 I/O(nonblocking IO)
  • I/O 多路復(fù)用( IO multiplexing)
  • 信號驅(qū)動 I/O( signal driven IO)
  • 異步 I/O(asynchronous IO)

注:由于signal driven IO在實(shí)際中并不常用,所以我這只提及剩下的四種IO Model。

  • 阻塞 I/O(blocking IO)

在linux中,默認(rèn)情況下所有的socket都是blocking,一個典型的讀操作流程大概是這樣:

Python3 協(xié)程_第2張圖片
recve時接收端會阻塞,直到系統(tǒng)接收到數(shù)據(jù),系統(tǒng)接收到數(shù)據(jù)后此時也是阻塞的,會從內(nèi)核緩存copy到用戶內(nèi)存,然后返回一個OK才是用戶真正接收到了數(shù)據(jù)。

當(dāng)用戶進(jìn)程調(diào)用了recvfrom這個系統(tǒng)調(diào)用,kernel就開始了IO的第一個階段:準(zhǔn)備數(shù)據(jù)(對于網(wǎng)絡(luò)IO來說,很多時候數(shù)據(jù)在一開始還沒有到達(dá)。比如,還沒有收到一個完整的UDP包。這個時候kernel就要等待足夠的數(shù)據(jù)到來)。這個過程需要等待,也就是說數(shù)據(jù)被拷貝到操作系統(tǒng)內(nèi)核的緩沖區(qū)中是需要一個過程的。而在用戶進(jìn)程這邊,整個進(jìn)程會被阻塞(當(dāng)然,是進(jìn)程自己選擇的阻塞)。當(dāng)kernel一直等到數(shù)據(jù)準(zhǔn)備好了,它就會將數(shù)據(jù)從kernel中拷貝到用戶內(nèi)存,然后kernel返回結(jié)果,用戶進(jìn)程才解除block的狀態(tài),重新運(yùn)行起來。

            
              所以,blocking IO的特點(diǎn)就是在IO執(zhí)行的兩個階段都被block了(等數(shù)據(jù)的階段和從內(nèi)核拷貝給用戶的階段)。
            
          
  • 非阻塞 I/O(nonblocking IO)

linux下,可以通過設(shè)置socket使其變?yōu)閚on-blocking。當(dāng)對一個non-blocking socket執(zhí)行讀操作時,流程是這個樣子:
Python3 協(xié)程_第3張圖片

當(dāng)用戶進(jìn)程發(fā)出read操作時,如果kernel中的數(shù)據(jù)還沒有準(zhǔn)備好,那么它并不會block用戶進(jìn)程,而是立刻返回一個error。從用戶進(jìn)程角度講 ,它發(fā)起一個read操作后,并不需要等待,而是馬上就得到了一個結(jié)果。用戶進(jìn)程判斷結(jié)果是一個error時,它就知道數(shù)據(jù)還沒有準(zhǔn)備好,于是它可以再次發(fā)送read操作。一旦kernel中的數(shù)據(jù)準(zhǔn)備好了,并且又再次收到了用戶進(jìn)程的system call(receive的動作),那么它馬上就將數(shù)據(jù)拷貝到了用戶內(nèi)存,然后返回。

            
              所以,nonblocking IO的特點(diǎn)是用戶進(jìn)程需要不斷的主動詢問kernel數(shù)據(jù)好了沒有。
            
          
  • IO多路復(fù)用

IO multiplexing就是我們說的select,poll,epoll,有些地方也稱這種IO方式為event driven IO。select/epoll的好處就在于單個process就可以同時處理多個網(wǎng)絡(luò)連接的IO。它的基本原理就是select,poll,epoll這個function會不斷的輪詢所負(fù)責(zé)的所有socket,當(dāng)某個socket有數(shù)據(jù)到達(dá)了,就通知用戶進(jìn)程。

在單線程且又是阻塞模式下,是沒法實(shí)現(xiàn)多個IO一起執(zhí)行的,因?yàn)楫?dāng)接收數(shù)據(jù)時,一直沒有接收到的話就會一直卡住。

在單線程下非阻塞模式下,假如此時有10個IO,對這10個IO進(jìn)行for循環(huán)來接收數(shù)據(jù),先接收其中2個IO的數(shù)據(jù),如果這2個IO沒有接收到數(shù)據(jù)就會返回err,此時就不會在阻塞了,然后繼續(xù)進(jìn)行for循環(huán),此時其他IO如果有數(shù)據(jù)就會將數(shù)據(jù)接收過來,然后就這樣不斷的receive,發(fā)現(xiàn)err就不阻塞,有數(shù)據(jù)則接收。使用非阻塞模式就可以處理多個socket,對于用戶來說就已經(jīng)是并發(fā)了。但是要注意的是第一階段不卡了,但是此時第二階段依然會卡,如果從內(nèi)核copy到用戶內(nèi)存的數(shù)據(jù)不大,則很快會copy完成,但是如果數(shù)據(jù)很大的話,第二階段就會一直在copy數(shù)據(jù),直到數(shù)據(jù)copy完成,但相應(yīng)的在第二階段卡的時間也會很久。

當(dāng)用戶進(jìn)程調(diào)用了select,那么整個進(jìn)程會被block,假如此時有100個socket的IO,那么kernel會監(jiān)視所有select負(fù)責(zé)的socket,當(dāng)任何一個socket中的數(shù)據(jù)準(zhǔn)備好了(kernel的數(shù)據(jù)準(zhǔn)備好),select就會返回。這個時候用戶進(jìn)程在調(diào)用read操作,將數(shù)據(jù)kernelcopy到用戶進(jìn)程。所以,I/O多路復(fù)用的特點(diǎn)是通過一種機(jī)制 一個進(jìn)程能同時等待多個文件描述符,而這些文件描述符(socket連接)其中的任意一個進(jìn)入讀就緒狀態(tài),select()函數(shù)就可以返回。

Python3 協(xié)程_第4張圖片
多路復(fù)用和阻塞模式的區(qū)別就是,阻塞模式監(jiān)視一個socket,有數(shù)據(jù)則接收;而多路復(fù)用就是可以通過select監(jiān)視N個socket,只要其中任何一個有數(shù)據(jù),則進(jìn)行select返回,然后receive數(shù)據(jù)(第二階段數(shù)據(jù)過大的話,依然會有阻塞)。

假如此時有10000個socket連接,監(jiān)視到有數(shù)據(jù)后kernel就會告訴返回給用戶進(jìn)程,但kernel不會告訴用戶進(jìn)程具體是哪個socket連接,所以用戶就會循環(huán)著10000個socket連接,但是即使其中只有2個socket有數(shù)據(jù),用戶程序也會去循環(huán)著10000個socket連接,這就造成了大量的多余循環(huán)操作。

select?
select最早于1983年出現(xiàn)在4.2BSD中,它通過一個select()系統(tǒng)調(diào)用來監(jiān)視多個文件描述符的數(shù)組,當(dāng)select()返回后,該數(shù)組中就緒的文件描述符便會被內(nèi)核修改標(biāo)志位,使得進(jìn)程可以獲得這些文件描述符從而進(jìn)行后續(xù)的讀寫操作。

select目前幾乎在所有的平臺上支持,其良好跨平臺支持也是它的一個優(yōu)點(diǎn),事實(shí)上從現(xiàn)在看來,這也是它所剩不多的優(yōu)點(diǎn)之一。

select的一個缺點(diǎn)在于單個進(jìn)程能夠監(jiān)視的文件描述符的數(shù)量存在最大限制,在Linux上一般為1024,不過可以通過修改宏定義甚至重新編譯內(nèi)核的方式提升這一限制。

另外,select()所維護(hù)的存儲大量文件描述符的數(shù)據(jù)結(jié)構(gòu),隨著文件描述符數(shù)量的增大,其復(fù)制的開銷也線性增長。同時,由于網(wǎng)絡(luò)響應(yīng)時間的延遲使得大量TCP連接處于非活躍狀態(tài),但調(diào)用select()會對所有socket進(jìn)行一次線性掃描,所以這也浪費(fèi)了一定的開銷。

poll?
poll在1986年誕生于System V Release 3,它和select在本質(zhì)上沒有多大差別,但是poll沒有最大文件描述符數(shù)量的限制。

poll和select同樣存在一個缺點(diǎn)就是,包含大量文件描述符的數(shù)組被整體復(fù)制于用戶態(tài)和內(nèi)核的地址空間之間,而不論這些文件描述符是否就緒,它的開銷隨著文件描述符數(shù)量的增加而線性增大。

另外,select()和poll()將就緒的文件描述符告訴進(jìn)程后,如果進(jìn)程沒有對其進(jìn)行IO操作,那么下次調(diào)用select()和poll()的時候?qū)⒃俅螆蟾孢@些文件描述符,所以它們一般不會丟失就緒的消息,這種方式稱為水平觸發(fā)(Level Triggered)。

epoll?
直到Linux2.6才出現(xiàn)了由內(nèi)核直接支持的實(shí)現(xiàn)方法,那就是epoll,它幾乎具備了之前所說的一切優(yōu)點(diǎn),被公認(rèn)為Linux2.6下性能最好的多路I/O就緒通知方法。

epoll可以同時支持水平觸發(fā)和邊緣觸發(fā)(Edge Triggered,只告訴進(jìn)程哪些文件描述符剛剛變?yōu)榫途w狀態(tài),它只說一遍,如果我們沒有采取行動,那么它將不會再次告知,這種方式稱為邊緣觸發(fā)),理論上邊緣觸發(fā)的性能要更高一些,但是代碼實(shí)現(xiàn)相當(dāng)復(fù)雜。

epoll同樣只告知那些就緒的文件描述符,而且當(dāng)我們調(diào)用epoll_wait()獲得就緒文件描述符時,返回的不是實(shí)際的描述符,而是一個代表就緒描述符數(shù)量的值,你只需要去epoll指定的一個數(shù)組中依次取得相應(yīng)數(shù)量的文件描述符即可,這里也使用了內(nèi)存映射(mmap)技術(shù),這樣便徹底省掉了這些文件描述符在系統(tǒng)調(diào)用時復(fù)制的開銷。

另一個本質(zhì)的改進(jìn)在于epoll采用基于事件的就緒通知方式。在select/poll中,進(jìn)程只有在調(diào)用一定的方法后,內(nèi)核才對所有監(jiān)視的文件描述符進(jìn)行掃描,而epoll事先通過epoll_ctl()來注冊一個文件描述符,一旦基于某個文件描述符就緒時,內(nèi)核會采用類似callback的回調(diào)機(jī)制,迅速激活這個文件描述符,當(dāng)進(jìn)程調(diào)用epoll_wait()時便得到通知。

epool會告訴用戶進(jìn)程具體哪個socket連接有數(shù)據(jù)了,所以用戶進(jìn)程不需要在將所有socket 連接全都循環(huán)一次才發(fā)現(xiàn)具體哪個有數(shù)據(jù)。

Windows不支持epool,支持select

  • 異步I/O

inux下的asynchronous IO其實(shí)用得很少。先看一下它的流程:

Python3 協(xié)程_第5張圖片
用戶進(jìn)程發(fā)起read操作之后,立刻就可以開始去做其它的事(不需要等待kernel拷貝數(shù)據(jù)到用戶)。而另一方面,從kernel的角度,當(dāng)它受到一個asynchronous read之后,首先它會立刻返回,所以不會對用戶進(jìn)程產(chǎn)生任何block。然后,kernel會等待數(shù)據(jù)準(zhǔn)備完成,然后kernel主動將數(shù)據(jù)拷貝到用戶內(nèi)存,當(dāng)這一切都完成之后,kernel會給用戶進(jìn)程發(fā)送一個signal,告訴它read操作完成了(沒有任何阻塞)。

  • 小結(jié)
    同步IO:阻塞、非阻塞、多路復(fù)用都屬于同步IO,因?yàn)樗麄兌夹枰却齥ernel到用戶的數(shù)據(jù)copy。同步IO都是需要用戶進(jìn)程去kernel 接收數(shù)據(jù)。
    異步IO:異步I/O不需要等待kernel到用戶的數(shù)據(jù)copy。異步是kernel主動將數(shù)據(jù)copy到用戶內(nèi)存。

異步因?yàn)閷?shí)現(xiàn)比較復(fù)雜,所以使用的較少,使用較多的還是epool多路復(fù)用。

阻塞模式:

            
              原理:內(nèi)核阻塞,直到接收到完整的數(shù)據(jù)后,在將數(shù)據(jù)copy給用戶內(nèi)存,copy完成后會返回一個OK給當(dāng)前server的進(jìn)程,然后進(jìn)程接觸阻塞繼續(xù)向下執(zhí)行(執(zhí)行代碼)。

server等待接收數(shù)據(jù)時,會處于阻塞的狀態(tài),當(dāng)前進(jìn)程不會再往下執(zhí)行,除非接收到數(shù)據(jù)以后才會;假如當(dāng)前有3個IO,目前第1個IO處于接收數(shù)據(jù)狀態(tài),除非第1個IO內(nèi)核接收完成,否則第2和第3個內(nèi)核IO都不會開始數(shù)據(jù)的接收;內(nèi)核準(zhǔn)備好數(shù)據(jù)還要講數(shù)據(jù)copy給用戶內(nèi)存。
            
          

非阻塞模式:

            
              原理:內(nèi)核不阻塞,用戶內(nèi)存接收數(shù)據(jù)會阻塞(直到接收完成);多個IO時,內(nèi)核會同時接收多個IO數(shù)據(jù),用戶進(jìn)程會輪詢的方式read內(nèi)核是否準(zhǔn)備好數(shù)據(jù),如果沒有準(zhǔn)備好的話內(nèi)核返回err給用戶進(jìn)程,此時用戶進(jìn)程會問內(nèi)核其他IO是否準(zhǔn)備好數(shù)據(jù),沒準(zhǔn)備好內(nèi)核返回err給用戶進(jìn)程,準(zhǔn)備的話就將數(shù)據(jù)copy給用戶內(nèi)存。

server等待接收數(shù)據(jù),假如當(dāng)前有3個IO,內(nèi)核的第1個IO數(shù)據(jù)沒有準(zhǔn)備好,用戶進(jìn)程向進(jìn)程詢問第1個IO數(shù)據(jù)是否準(zhǔn)備好,內(nèi)核返回err給用戶進(jìn)程,用戶進(jìn)程去問內(nèi)核第2個IO數(shù)據(jù)是否準(zhǔn)備好,準(zhǔn)備好了就會將數(shù)據(jù)copy到用戶內(nèi)存,用戶進(jìn)程以此類推的循環(huán)去詢問內(nèi)核;  需要不斷的read和返回err,開銷較大。
            
          

IO多路復(fù)用:
select:

            
                  有多個socket IO時,通過select來負(fù)責(zé)所有socket IO,然后內(nèi)核會監(jiān)視所有select負(fù)責(zé)的socket,用戶進(jìn)程會循環(huán)所有IO,當(dāng)任何一個socket IO中的數(shù)據(jù)準(zhǔn)備好了(相當(dāng)于內(nèi)核的數(shù)據(jù)準(zhǔn)備好了)且剛好被用戶進(jìn)程循環(huán)到,select就會返回datagram ready給用戶進(jìn)程(此時用戶內(nèi)存處于阻塞狀態(tài)),用戶進(jìn)程將該IO數(shù)據(jù)copy到用戶內(nèi)存,copy完成后返回OK給用戶進(jìn)程,告知解除用戶內(nèi)存,讓其他IO在內(nèi)核內(nèi)存準(zhǔn)備好的數(shù)據(jù)可以copy到用戶內(nèi)存(如果當(dāng)前從內(nèi)核copy到用戶內(nèi)存的數(shù)據(jù)較大的話,只能等待數(shù)據(jù)copy完成,也就是該階段依然是阻塞,除非數(shù)據(jù)copy完成后,才會為其他IO的數(shù)據(jù)進(jìn)行copy操作)。
使用select不需要內(nèi)核返回大量的err,但是用戶進(jìn)程依然需要循環(huán)所有IO,假如10000個IO,其中只有2個IO數(shù)據(jù)準(zhǔn)備好了,那么用戶進(jìn)程依然需要去循環(huán)這10000個IO來發(fā)現(xiàn)這兩個已經(jīng)準(zhǔn)備好數(shù)據(jù)的IO。
select還是存在大量循環(huán)的操作。
            
          

poll:

            
                  poll和select基本相似,select支持的文件描述符(相當(dāng)于每個IO的索引地址,用戶進(jìn)程需要根據(jù)具體的地址來進(jìn)行制定的數(shù)據(jù)操作)為1024,poll則沒有文件描述符的限制。
            
          

epoll:

            
                  不需要用戶進(jìn)程去循環(huán),當(dāng)內(nèi)核數(shù)據(jù)準(zhǔn)備好后會立即告知用戶進(jìn)程具體的哪個socket IO數(shù)據(jù)準(zhǔn)備好了,且只會說一遍,不會再次告知,用戶進(jìn)程不需要循環(huán)所有socket IO ,不過epoll的代碼實(shí)現(xiàn)相當(dāng)復(fù)雜。
            
          

異步IO:

            
              用戶進(jìn)程發(fā)起read后就立刻開始做其他事情,不需要等內(nèi)核將數(shù)據(jù)copy到用戶;而內(nèi)核接到read后會返回信息給用戶進(jìn)程,不會讓用戶進(jìn)程產(chǎn)生阻塞(這樣第二階段不會阻塞),然后當(dāng)內(nèi)核準(zhǔn)備好數(shù)據(jù)后,內(nèi)核會主動將數(shù)據(jù)copy給用戶內(nèi)存(其他模式是用戶主動從內(nèi)核copy數(shù)據(jù)),當(dāng)數(shù)據(jù)copy完成后內(nèi)核會發(fā)送一個signal給用戶進(jìn)程,告訴用戶進(jìn)程read操作完成了(沒有任何阻塞)。
異步因?yàn)閷?shí)現(xiàn)比較復(fù)雜,所以使用的較少,使用較多的還是epool多路復(fù)用。
            
          

select IO多路復(fù)用

server端

            
              # 服務(wù)器端
import select
import socket
import sys
import queue


server = socket.socket()
server.setblocking(0)

server_addr = ('localhost',10000)

print('starting up on %s port %s' % server_addr)
server.bind(server_addr)

server.listen(5)


inputs = [server, ] #自己也要監(jiān)測呀,因?yàn)閟erver本身也是個fd
outputs = []

message_queues = {}

while True:
    print("waiting for next event...")

    readable, writeable, exeptional = select.select(inputs,outputs,inputs) #如果沒有任何fd就緒,那程序就會一直阻塞在這里

    for s in readable: #每個s就是一個socket

        if s is server: #別忘記,上面我們server自己也當(dāng)做一個fd放在了inputs列表里,傳給了select,如果這個s是server,代表server這個fd就緒了,
            #就是有活動了, 什么情況下它才有活動? 當(dāng)然 是有新連接進(jìn)來的時候 呀
            #新連接進(jìn)來了,接受這個連接
            conn, client_addr = s.accept()
            print("new connection from",client_addr)
            conn.setblocking(0)
            inputs.append(conn) #為了不阻塞整個程序,我們不會立刻在這里開始接收客戶端發(fā)來的數(shù)據(jù), 把它放到inputs里, 下一次loop時,這個新連接
            #就會被交給select去監(jiān)聽,如果這個連接的客戶端發(fā)來了數(shù)據(jù) ,那這個連接的fd在server端就會變成就續(xù)的,select就會把這個連接返回,返回到
            #readable 列表里,然后你就可以loop readable列表,取出這個連接,開始接收數(shù)據(jù)了, 下面就是這么干 的

            message_queues[conn] = queue.Queue() #接收到客戶端的數(shù)據(jù)后,不立刻返回 ,暫存在隊(duì)列里,以后發(fā)送

        else: #s不是server的話,那就只能是一個 與客戶端建立的連接的fd了
            #客戶端的數(shù)據(jù)過來了,在這接收
            data = s.recv(1024)
            if data:
                print("收到來自[%s]的數(shù)據(jù):" % s.getpeername()[0], data)
                message_queues[s].put(data) #收到的數(shù)據(jù)先放到queue里,一會返回給客戶端
                if s not  in outputs:
                    outputs.append(s) #為了不影響處理與其它客戶端的連接 , 這里不立刻返回?cái)?shù)據(jù)給客戶端


            else:#如果收不到data代表什么呢? 代表客戶端斷開了呀
                print("客戶端斷開了",s)

                if s in outputs:
                    outputs.remove(s) #清理已斷開的連接

                inputs.remove(s) #清理已斷開的連接

                del message_queues[s] ##清理已斷開的連接


    for s in writeable:
        try :
            next_msg = message_queues[s].get_nowait()

        except queue.Empty:
            print("client [%s]" %s.getpeername()[0], "queue is empty..")
            outputs.remove(s)

        else:
            print("sending msg to [%s]"%s.getpeername()[0], next_msg)
            s.send(next_msg.upper())


    for s in exeptional:
        print("handling exception for ",s.getpeername())
        inputs.remove(s)
        if s in outputs:
            outputs.remove(s)
        s.close()

        del message_queues[s]
            
          

client端

            
              import socket
import sys

messages = [ b'This is the message. ',
             b'It will be sent ',
             b'in parts.',
             ]
server_address = ('localhost', 10000)

# 創(chuàng)建一個 TCP/IP socket
socks = [ socket.socket(socket.AF_INET, socket.SOCK_STREAM) for i in range(50)]  # 同時開啟50個socket

print('connecting to %s port %s' % server_address)
for s in socks:
    s.connect(server_address)

for message in messages:

    # 發(fā)送消息
    for s in socks:
        print('%s: sending "%s"' % (s.getsockname(), message) )
        s.send(message)

    # 接收消息
    for s in socks:
        data = s.recv(1024)
        print( '%s: received "%s"' % (s.getsockname(), data) )
        if not data:
            print(sys.stderr, 'closing socket', s.getsockname() )
            
          

selectors模塊

該模塊允許基于選擇模塊原語的高級和高效I / O復(fù)用。 建議用戶使用此模塊,除非他們希望精確控制所使用的操作系統(tǒng)級基元。

            
              import selectors
import socket
 
sel = selectors.DefaultSelector()
 
def accept(sock, mask):
    conn, addr = sock.accept()  
    print('accepted', conn, 'from', addr)
    conn.setblocking(False)
    sel.register(conn, selectors.EVENT_READ, read)
 
def read(conn, mask):
    data = conn.recv(10000)  
    if data:
        print('echoing', repr(data), 'to', conn)
        conn.send(data)  # Hope it won't block
    else:
        print('closing', conn)
        sel.unregister(conn)
        conn.close()
 
sock = socket.socket()
sock.bind(('localhost', 10000))
sock.listen(100)
sock.setblocking(False)
sel.register(sock, selectors.EVENT_READ, accept)
 
while True:
    events = sel.select()
    for key, mask in events:
        callback = key.data
        callback(key.fileobj, mask)
            
          

?


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯(lián)系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦!??!

發(fā)表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 亚洲欧美国产另类视频 | 成人毛片久久 | 国产九色在线观看 | 九九热综合 | 欧美成人a∨高清免费观看 久久亚洲欧美日韩精品专区 | 国产高清美女一级a毛片久久 | 搡女人的高清免费视频 | 国产午夜亚洲精品国产 | 美女用震蛋叫爽的视频95视频 | 三级国产精品一区二区 | 亚洲 欧美 日韩 在线 香蕉 | 嫩草影院永久入口在线观看 | 久久日本精品一区二区三区 | 久久精品视频99 | 97精品一区二区 | 色婷婷综合久久久中字幕精品久久 | 久久精品道一区二区三区 | 9久热这里只有精品免费 | 亚洲涩涩 | 九九九九精品视频在线播放 | 欧美精品免费xxxxx视频 | 日韩视频在线观看免费 | 亚洲免费在线播放 | 日本中文字幕一区二区有码在线 | 中文字幕在线免费观看 | 看一下毛片| 精品欧美一区二区在线观看 | 国产综合亚洲精品一区二 | 久草在线精品ac | 免费观看国产大片资源视频 | 大逼逼影院 | 成人18免费观看的软件 | 国产精品一区二区三区久久 | 色综合网亚洲精品久久久 | 欧美日日日 | 亚洲日韩精品AV无码富二代 | 黄色网一级片 | 日韩一道本 | 成人免费看 | 欧美在线资源 | 日本精品一区二区三区四区 |