python同步編程和異步編程

一 簡介

1 同步和異步

函數或方法掉調用的時候,被調用者是否能獲得最終結果來判斷同步和異步
直接獲得最終結果的,就是同步調用
不直接獲得最終結果的,就是異步調用html

同步就是我讓你打飯,你不打好我就不走開,直到你打飯給了我
異步就是我讓你打飯,你等着,我不等你,可是我會盯着你,你打完我會過來拿走,異步並不能保證多長時間將飯打完。python

異步給的是臨時結果,目前是拿不到的
同步只看結果是否是最終結果進行判斷 web

2 阻塞,非阻塞

函數或方法調用的時候,是否當即返回
當即返回就是非阻塞調用
不當即返回就是阻塞調用 編程

3 區別

同步,異步,阻塞,非阻塞 不相關
同步異步強調的是結果
阻塞,非阻塞強調的是時間,是否等待 windows

同步和異步的區別在於:調用者是否獲得可想要的結果緩存

同步就是一直要執行到返回結果 網絡

異步就是直接返回了,可是不是最終結果,調用者不能經過這種調用方式獲得結果,仍是須要經過被調用者,使用其餘方式通知調用者,來取回最終的結果 session

同步阻塞:我啥事也不幹,就等你打飯給我,打飯是結果,並且我啥事也不敢就一直等,同步加阻塞。多線程

同步非阻塞:我等着你打飯給我,但我能夠完手機,看電視,打飯是結果,但我不一直等 併發

異步阻塞: 我要打飯,你說等號,並無給我返回飯,我啥事也不幹,就等着飯好了叫我,叫號。

異步非阻塞:我要打飯,你說等號,並無返回飯,我在旁邊看電視,玩手機,反打好了叫我。

4 同步IO,異步IO,IO 多路複用

1 IO 兩個階段

1 數據準備階段
2 內核空間複製會用戶進程緩衝區階段

2 發生IO的時候

1 內核從輸入設備讀寫數據
2 進程從內核複製數據
系統調用read 函數
第一個IO阻塞的函數是input函數,是一個同步阻塞模型,網絡也是一個IO,標準輸入,標準輸出等也IO

5 零拷貝

1 零拷貝概念

CPU 不執行拷貝數據從一個存儲區域到另外一個存儲區域的任務,這一般用於經過網絡傳輸一個文件時用於減小CPU週期和內存帶寬。

操做系統某些組件(例如驅動程序、文件系統和網絡協議棧)若採用零複製技術,則能極大地加強了特定應用程序的性能,並更有效地利用系統資源。經過使CPU得以完成其餘而非將機器中的數據複製到另外一處的任務,性能也獲得了加強。另外,零複製操做減小了在用戶空間與內核空間之間切換模式的次數。

零複製協議對於網絡鏈路容量接近或超過CPU處理能力的高速網絡尤其重要。在這種網絡下,CPU幾乎將全部時間都花在複製要傳送的數據上,所以將成爲使通訊速率低於鏈路容量的瓶頸。

2 零拷貝帶來的好處

1 減小甚至徹底避免沒必要要的CPU拷貝,從而讓CPU 解脫出來去執行其餘任務
2 減小內存帶寬佔用
3 一般零拷貝技術還能減小用戶空間和內核空間之間的上下文切換

3 Linux 系統的"用戶空間"和"內核空間"

從Linux系統來看,除了引導系統的BIN區,整個內存空間主要被分紅兩部分:

1 內核空間(kernel space ) : 主要提供給程序調度,內存分配,鏈接硬件資源等程序邏輯空間

2 用戶空間 (user space): 提供給各個進程的主要空間,用戶空間不具有訪問內核空間資源的權限,所以若是應用程序須要使用到內核空間的資源,則須要經過系統調度來完成,從用戶空間切換到內核空間,而後在完成操做後再從內核空間切換到用戶空間

4 Linux 中的零拷貝技術的實現方向

1 直接I/O: 對於這種傳輸方式來講,應用程序能夠直接訪問硬件存儲,操做系統內核只是輔助數據傳輸,這種方式依舊存在用戶空間和內核空間的上下文切換,但硬件上的數據不會拷貝到內核空間,而是直接拷貝到可用戶空間,所以直接IO不存在內核空間緩衝區和用戶空間緩衝區之間的數據拷貝


2 在數據傳輸過程當中,避免數據在用戶空間緩衝區和內核空間緩衝區之間的CPU拷貝,以及數據在系統內核空間的CPU拷貝,


3 copy-on-write(寫時複製技術):在某些狀況下,Linux操做系統的內核緩衝區可能被多個應用程序共享,操做系統有可能會將用戶空間緩衝區地址映射考內核空間緩衝區,當應用程序須要對共享的數據進行修改時,才須要真正的拷貝數據到應用程序的用戶空間緩衝區中,而且對本身的用戶空間的緩衝區的數據進行修改不會影響到其餘共享數據的應用程序,因此,若是應用程序不須要對數據進行任何修改,就不會存在數據從系統內核空間緩衝區拷貝到用戶空間緩衝區的操做。

對於零拷貝技術是否實現主要依賴於操做系統底層是否提供相應的支持。

5 傳統I/O 操做

1 發起read系統調用: 致使用戶空間到內核空間的上下文切換(第一次上下文切換),經過DMA引擎將文件中的數據從磁盤上讀取到內核空間緩衝區(第一次拷貝:hand drive ----> kernel buffer)


2 將內核空間緩衝區的數據拷貝到用戶空間緩衝區中(第二次拷貝: kernel buffer ---> user buffer),而後read系統調用返回,而系統調用的返回又會致使一次內核空間到用戶空間的上下文切換(第二次上下文切換)


3 發出write系統調用: 致使用戶空間到內核空間的上下文切換(第三次上下文切換),將用戶空間緩衝區的數據拷貝到內核空間中於socket相關的緩衝區中,(及第二步從內核空間緩衝區拷貝的數據原封不動的再次拷貝到內核空間的socket緩衝區中)( 第三次拷貝: user buffer--> socket buffer)


4 write 系統調用返回,致使內核空間到用戶空間的再次上下文切換(第四次上下文切換),經過DMA引擎將內核緩衝區中的數據傳遞到協議引擎(第四次拷貝:socket buffer -> protocol engine ),此次拷貝時獨立的異步的過程。


事實上調用的返回並不保證數據被傳輸,甚至不保證數據傳輸的開始,只是意味着將我麼要發送的數據放入到了一個待發送的隊列中,除非實現了優先環或者隊列,不然會是先進先出的方式發送數據的。

總的來講,傳統的I/O操做進行了4次用戶空間與內核空間的上下文切換,以及4次數據拷貝。其中4次數據拷貝中包括了2次DMA拷貝和2次CPU拷貝。

傳統模式爲什麼將數據從磁盤讀取到內核空間而不是直接讀取到用戶空間緩衝區,其緣由是爲了減小IO操做以提升性能,由於OS會根據局部性原理一次read() 系統調用的時候預讀取更多的文件數據到內核空間緩衝區中,這樣當下一次read()系統調用的時候發現要讀取的數據已經存在於內核空間緩衝區的時候只須要直接拷貝數據到用戶空間緩衝區便可,無需再進行一次低效的磁盤IO操做。

Bufferedinputstream 做用是會根據狀況自動爲咱們預讀取更多的數據到他本身維護的一個內部字節數據緩衝區,這樣能減小系統調用次數來提升性能。

總的來講,內核緩衝區的一大做用是爲了減小磁盤IO操作,Bufferedinputstream 則是減小"系統調用"

6 DMA

DMA(direct memory access) --- 直接內存訪問,DMA 是容許外設組件將IO數據直接傳送到主存儲器並而且傳輸不須要CPU參與,以此解放CPU去作其餘的事情。
而用戶空間與內核空間之間的數據傳輸並無相似DMA這種能夠不須要CPU參與的傳輸工具,所以用戶空間與內核空間之間的數據傳輸是須要CPU全程參與的。全部也就有了經過零拷貝技術來減小和避免沒必要要的CPU數據拷貝過程。

7 經過sendfile 實現零拷貝IO

1 發起sendfile系統調用,致使用戶空間到內核空間的上下文切換(第一次上下文切換),經過DMA引擎將磁盤文件中的內容拷貝到內核空間緩衝區中(第一次拷貝: hard drive --> kernel buffer)而後再將數據從內核空間拷貝到socket相關的緩衝區中,(第二次拷貝,kernel ---buffer --> socket buffer)


2 sendfile 系統調用返回,致使內核空間到用戶空間的上下文切換(第二次上下文切換)。經過DMA 引擎將內核空間的socket緩衝區的數據傳遞到協議引擎(第三次拷貝:socket buffer-> protocol engine )


總的來講,經過sendfile實現的零拷貝I/O只使用了2次用戶空間與內核空間的上下文切換,以及3次數據的拷貝。其中3次數據拷貝中包括了2次DMA拷貝和1次CPU拷貝。

Q:但經過是這裏仍是存在着一次CPU拷貝操做,即,kernel buffer ——> socket buffer。是否有辦法將該拷貝操做也取消掉了?
A:有的。但這須要底層操做系統的支持。從Linux 2.4版本開始,操做系統底層提供了scatter/gather這種DMA的方式來從內核空間緩衝區中將數據直接讀取到協議引擎中,而無需將內核空間緩衝區中的數據再拷貝一份到內核空間socket相關聯的緩衝區中。

8 帶有DMA 收集拷貝功能的sendfile 實現的IO

從Linux 2.4 開始,操作系統底層提供了帶有scatter/gather 的DMA來從內核空間緩衝區中將數據讀取到協議引擎中,這樣以來待傳輸的數據能夠分散再存儲的不一樣位置,而不須要再連續存儲中存放,那麼從文件中讀出的數據就根本不須要被拷貝到socket緩衝區中去,只是須要將緩衝區描述符添加到socket緩衝區中去,DMA收集操做會根據緩衝區描述符中的信息將內核空間中的數據直接拷貝到協議引擎中


1 發出sendfile 系統調用,致使用戶空間到內核空間的上下文切換,經過DMA 引擎將磁盤文件內容拷貝到內核空間緩衝區中(第一次拷貝: hard drive -> kernel buffer)


2 沒有數據拷貝到socket緩衝區,取而代之的是隻有向相應的描述信息被拷貝到相應的socket緩衝區中,該描述信息包含了兩個方面: 1 kernel buffer 的內存地址 2 kernel buffer 的偏移量。


3 sendfile 系統調用返回,致使內核空間到用戶空間的上下文切換(第二次上下文切換),DMA gather copy 根據 socket緩衝區中描述符提供的位置和偏移量信息直接將內核空間的數據拷貝到協議引擎上(kernel buffer --> protocol engine),這樣就避免了最後依次CPU數據拷貝


總的來講,帶有DMA收集拷貝功能的sendfile實現的I/O只使用了2次用戶空間與內核空間的上下文切換,以及2次數據的拷貝,並且這2次的數據拷貝都是非CPU拷貝。這樣一來咱們就實現了最理想的零拷貝I/O傳輸了,不須要任何一次的CPU拷貝,以及最少的上下文切換。

在Linux 2.6.33 版本以前sendfile支持文件到套接字之間的傳輸,及in_fd 至關於一個支持mmap的文件,out_fd 必須是一個socket,但從Linux 2.6.33版本開始,out_fd 能夠是任意類型文件描述符,因此從Linux 2.6.33 版本開始sendfile 能夠支持文件到文件,文件到套接字之間的數據傳輸。

9 傳統I/O 和零拷貝及sendfile零拷貝I/O比較

傳統I/O經過兩條系統指令read、write來完成數據的讀取和傳輸操做,以致於產生了4次用戶空間與內核空間的上下文切換的開銷;而sendfile只使用了一條指令就完成了數據的讀寫操做,因此只產生了2次用戶空間與內核空間的上下文切換。
傳統I/O產生了2次無用的CPU拷貝,即內核空間緩存中數據與用戶空間緩衝區間數據的拷貝;而sendfile最多隻產出了一次CPU拷貝,即內核空間內之間的數據拷貝,甚至在底層操做體系支持的狀況下,sendfile能夠實現零CPU拷貝的I/O。
因傳統I/O用戶空間緩衝區中存有數據,所以應用程序可以對此數據進行修改等操做;而sendfile零拷貝消除了全部內核空間緩衝區與用戶空間緩衝區之間的數據拷貝過程,所以sendfile零拷貝I/O的實現是完成在內核空間中完成的,這對於應用程序來講就沒法對數據進行操做了。
Q:對於上面的第三點,若是咱們須要對數據進行操做該怎麼辦了?
A:Linux提供了mmap零拷貝來實現咱們的需求

10 經過mmap 實現零拷貝I/O

Mmap(內存映射)是一個比sendfile昂貴但優於傳統IO的方式

1 發出mmap系統調用,致使用戶空間到內核空間的上下文切換(第一次上下文切換)。經過DMA引擎將磁盤文件中的內容拷貝到內核空間緩衝區中(第一次拷貝: hard drive ——> kernel buffer)。


2 mmap 系統調用返回,致使內核空間到用戶空間的上下文切換(第二次上下文切換),接着用戶空間和內核空間共享這個緩衝區,而不須要將數據從內核空間拷貝到用戶空間,所以用戶空間和內核空間共享的緩衝區


3 發出write 系統調用紅,致使用戶空間到內核空間第三次上下文切換,將數據從內核空間拷貝到內核空間的socket相關的緩衝區(第二次拷貝:kernel buffer ----> socket buffer )


4 write 系統調用返回,致使內核空間到用戶空間的上下文切換(第四次上下文切換),經過DMA 引擎將內核空間socket緩衝區的數據傳遞到協議引擎(第三次拷貝: socket buffer---> protocol engine)

總的來講,經過mmap實現的零拷貝I/O進行了4次用戶空間與內核空間的上下文切換,以及3次數據拷貝。其中3次數據拷貝中包括了2次DMA拷貝和1次CPU拷貝。

6 同步IO

1 同步阻塞IO

python同步編程和異步編程

在文件讀取進入內核空間和從內核空間拷貝進入用戶進程空間的過程當中,沒有任何的數據返回,客戶端在一直等待狀態。

2 同步非阻塞

python同步編程和異步編程

進程調用read操做,若是IO沒有準備好,當即返回ERROR,進程不阻塞,用戶能夠再次發起系統調用,若是內核已經準備好,就阻塞,而後複製數據到用戶空間

第一階段數據沒準備好,就先忙別的,等會再看看,檢查數據是否準備好了的過程是非阻塞的

第二階段是阻塞的,及內核空間和用戶空間之間複製數據是阻塞的,可是要等待飯盛好纔是完事,這是同步的。

3 IO 多路複用

python同步編程和異步編程

所謂的IO多路複用,就是同時監控多個IO,有一個準備好了,就不須要等待開始處理,提升了同時處理IO的能力

select是全部平臺都支持,poll是對select的升級

epoll,Linux 系統內核2.5+ 開始支持,對select和epoll的加強,在監視的基礎上,增長了回調機制,BSD,Mac的kqueue,還有windows的iocp

若是既想訪問網絡,又想訪問文件,則先將準備好的數據先處理,那個準備好了就處理那個

可以提升同時處理IO的能力,誰先作玩我先處理誰

上面的兩種方式,效率太差了,等完一個完成後再等一個,太慢了。

誰好了處理誰,不一樣的平臺對IO多路複用的實現方式是不一樣的

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

通常來將select和poll 在同一個層次,epoll是Linux中存在的


select原理

1 將關注的IO操做告訴select函數並調用,進程阻塞,內核監視select關注的文件,描述符FD,被關注的任何一個FD對應的IO準備好了數據,select就返回,在使用read將數據複製到用用戶進程。其select模式下的準備好的通知是沒有針對性的,須要用戶本身找到是不是本身的並進行處理。select作到的是時間重疊

epoll增長了回調機制,那一路準備好了,我會告訴你,有一種是你不用管了,好了我直接替你調用。

7 異步調用

python同步編程和異步編程

兩個階段
等待數據準備和拷貝階段
當即返回數據,給一個號。到時候叫號,直接返回
信號句柄,告訴你幾號好了,(signal handler process datagram)
有些時候是須要爭搶的
我能夠不通知你,我也能夠通知你後你再來
理解數據層面的東西,就不要理解其餘的socket層面的東西
文件中實際就是兩個緩衝隊列,每一個隊列是一個。
在異步模型中,操做系統通你的,你是在用戶空間的,操做系統能夠是在內核空間的,進程和線程等等的都是操做系統層面的東西。
整個過程當中進程均可以作其餘的事,就算是通知了,也不必定要當即反應,這和你的設置有關
Linux中的AIO 的系統調用,內核版本從2.6開始支持
通常的IO是IO多路複用和異步複用

二 python中的IO 多路複用

1 簡介

IO 多路複用
大多數操做系統都支持select和poll
Linux 2.5+ 支持epoll
BSD,Mac支持kqueue
Windows 的 iocp
python的select庫

實現了select,poll系統調用,這個基本上操做系統都支持,部分實現了epoll,底層的IO多路複用模塊

開發中的選擇

1 徹底跨平臺,select 和poll ,但其性能較差
2 針對不一樣的操做系統自行選擇支持技術,這樣會提升IO處理能力

selectors庫
3.4 版本後提供這個庫,高級的IO複用庫
類層次結構

BaseSelector
+-- SelectSelector 實現select
+-- PollSelector 實現poll
+-- EpollSelector 實現epoll
+-- DevpollSelector 實現devpoll
+-- KqueueSelector 實現kqueue

selectors.DefaultSelector返回當前平臺最有效,性能最最高的實現
可是因爲沒有實現windows的IOCP,因此只能退化爲select。
默認會自適應,其會選擇最佳的方式,Linux 會直接選擇 epoll ,經過此處,能拿到平臺的最優方案。

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註冊一個文件獨享,監視它的IO事件
fileobj 被監視的文件對象,如socket對象
events 事件,該文件對象必須等待的事件,read或write

python同步編程和異步編程

data 可選的與此文件對象相關的不透明數據,如可用來存儲每一個客戶端的會話ID,能夠是函數,類,實例,若是是函數,有點回調的意思,通知某個函數,某個實例,某個類,能夠是類屬性,等,均可以,None表示消息發生了,沒人認領。

3 基本實現socket 操做監控

1 思路

第一步 :須要實例化 ,選擇一個最優的實現,將其實例化(選擇不一樣平臺實現的IO複用的最佳框架),python內部處理


第二步:註冊函數,將要監控對象,要監控事件和監控觸發後對象寫入register註冊中

1 註冊: 對象,啥事件,調用的函數
2 進行循環和監控select函數的返回,當監控的對象的事件知足時會當即返回,在events中能夠拿到這些數據events中有我是誰,我是什麼事件觸發的(讀和寫),讀的知足能夠recv,key 是讓我監控的東西,event是其什麼事件觸發的。將對象和事件拿到後作相應的處理。


第三步:實時關注socket有讀寫操做,從而影響events的變化

對socket來判斷有沒有讀,若讀了,則直接觸發對應的機制進行處理。一旦有新的鏈接準備,則會將其消息發送給對應的函數進行處理相關的操做。被調用的函數是有要求的,其須要傳送mask的,data 就是將來要調用的函數,創建了事件和將來參數之間創建的關係。

Accept 自己就是一個read事件
Selector 會調用本身的select函數進行監視,這個函數是阻塞的,當數據已經在內核緩衝區準備好了,你就能夠讀取了,這些事給select進行處理

在註冊的時候,後面加了data,後面直接使用,直接調用,不用管其餘,data和每個觀察者直接對應起來的。

只要有一個知足要求,直接返回

讀事件指的是in操做,及就是當有鏈接的時候

當通知成功後,其函數內部是不會阻塞了,等待通知,通知成功後就不會阻塞了。此處的data至關於直接帶着窗口號,直接進行處理,而不須要一個一個的遍歷

當一個知足了,就不會阻塞了。events: 兩個IO都知足,等待幾路,幾路的IO都在此處,若是知足,則直接向下打印events,其中key是註冊的惟一的東西,socket 也能夠,可是能夠定義socket的讀和寫,通常都是合着的


第四步:調用對應事件的對象,並執行相關操做

而後將events拿出來解構,key自己是一個多元祖,key上保存着註冊塞進去的data,key是存儲了4個信息的元祖,此處的data稱爲回調函數,加上() 稱爲調用

2 代碼實現

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

代碼下載目錄
IO 多路複用初始代碼

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

3 基本參數講解

1 events參數:

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 及套接字返回的相關參數,和以前的socket中的accpet中的conn 類似,

fd 及文件描述符

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

data 及註冊調用的函數,上述的有accept 和recv 函數


第二組:
1 events 的狀態,及mask

2 select.get_map() 參數:

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和上面的列表中的二元祖中的前一個徹底相同


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

4 總結:

IO 多路複用就是一個線程來處理全部的IO
在單線程中進行處理IO多路複用
多線程中的IO阻塞時浪費CPU資源,其是等待狀態,等待狀態雖然不佔用CPU資源,但線程自己的狀態須要維持,仍是會佔用必定的資源

4 改進版本的socket 監控

1 描述

send 是寫操做,有可能阻塞,也能夠監聽
recv所在的註冊函數,要監聽python同步編程和異步編程讀與寫事件,回調的時候,須要mask 來判斷到底是讀觸發了仍是寫觸發了,因此,須要修改方法聲明,增長mask
寫操做當發送羣聊時,其每一個連接是獨立的,須要queue隊列保存相關的數據,並進行接受和發送操做

2 代碼實現

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

python同步編程和異步編程

IO 多路複用最終代碼

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

三 異步編程

1 asyncio 簡介

3.4 版本加入標準庫
asyncio 底層是基於selectors實現的,看似庫,其實就是一個框架,包括異步IO,事件循環,協程,任務等


並行和串行的區分:
兩個事件的因果關係:
如有因果關係,則可使用串行
若無因果關係,則可使用並行,及多線程來處理

2 相關參數及詳解

參數 含義
asyncio.get_event_loop() 返回一個事件循環對象,是asyncio.BaseEventLoop的實例
AbstractEventLoop.stop() 中止運行事件循環
AbstractEventLoop.run_forever() 一直運行,直到stop()
AbstractEventLoop.run_until_complete(future) 運行直到Future對象運行完成
AbstractEventLoop.close() 關閉事件循環
AbstractEventLoop.is_running() 返回事件循環是否運行
AbstractEventLoop.close() 關閉事件

3 協程

1 基本實例

#!/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同步編程和異步編程

此處的默認執行順序是a()到b()的順序執行,若要使其交叉執行,則須要使用yield 來實現

實現方式以下

#!/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同步編程和異步編程

上述實例中經過生成器完成了調度,讓兩個函數都幾乎同時執行,這樣的調度不是操做系統進行的。而是用戶本身設計完成的


這個程序編寫要素:
1 須要使用yield來讓出控制權
2 須要循環幫助執行

2 協程簡介

協程不是進程,也不是線程,它是用戶空間調度的完成併發處理的方式。
進程,線程由操做系統完成調度,而協程是線程內完成調度的,不須要更多的線程,天然也沒有多線程切換的開銷
協程是非搶佔式調度,只有一個協程主動讓出控制權,另外一個協程纔會被調度。
協程也不須要使用鎖機制,由於其是在同一個線程中執行的
多CPU下,可使用多進程和協程配合,既能進程併發,也能發揮出協程在單線程中的優點。
python中的協程是基於生成器的。

3 協程基本書寫

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()

結果以下

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()

結果以下

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)) # 此處判斷是不是函數,和調用無關

a=a()
print (asyncio.iscoroutine(a))  # 此處是判斷對象,是調用後的結果
loop=asyncio.get_event_loop()
task=[a,b()]
loop.run_until_complete(asyncio.wait(task))
loop.close()

結果以下

python同步編程和異步編程

async def 用來定義協程函數,iscoroutinefunction()返回True,協程函數中能夠不包含await,async關鍵字,可是不能使用yield關鍵字
若是生成器函數調用返回生成器對象同樣,協程函數調用也會返回一個對象成爲協程對象,iscoroutine()返回爲True

4 TCP echo server 實現

#!/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) # 接受客戶端的數據,至關於recv,wait 就是IO等待,此處會等待
        print (conn,send)
        client_addr=send.get_extra_info('peername')  # 獲取客戶端信息
        msg="{} {}".format(data.decode(),client_addr).encode()  #封裝消息
        send.write(msg)  # 傳輸到客戶端
        await    send.drain()  # 此處至關於makefile中的flush ,此處也會IO等待

loop=asyncio.get_event_loop() #實例化一個循環事件
crt=asyncio.start_server(handler,ip,port,loop=loop) #使用異步方式啓動函數,最後一個參數是應該用誰來循環處理
server=loop.run_until_complete(crt) # 此處是直到此方法完成後終止

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

5 擴展aiohttp 庫

異步的http 庫,使用協程實現的
須要安裝第三方模塊 aiohttp

pip  install  aiohttp

http server 基礎實現

#!/usr/bin/poython3.6
#conding:utf-8

from  aiohttp  import web
async def  indexhandle(request:web.Request):  # 處理客戶端請求函數
    print("web",web.Request)
    return web.Request(text=request.path,status=201) #返回文本和狀態碼

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)  # 此處是返回給客戶端的數據,後面的0000是默認

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)  #監聽IP和端口並運行

客戶端實現

#!/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,要和服務端通訊,必須先獲取session,以後才能進行相關的操做 ,此處使用with是打開關閉會話,保證會話可以被關閉。
        async with  session.get(url)  as res:  # 須要這個URL資源,獲取,
            print (res.status)  # 此處返回爲狀態碼
            print (await  res.text())  # 此處返回爲文本信息

url='http://www.baidu.com'

loop=asyncio.get_event_loop()
loop.run_until_complete(get_html(url))
loop.close()
相關文章
相關標籤/搜索