例子1: follow.py 可以使用生成器完成 tail -f 的功能,也就是跟蹤輸出的功能。

例子2: 生成器用作程序管道(類似unix pipe)

pipeline.py

理解pipeline.py
在pipeline中,follow函數和grep函數相當於程序鏈,這樣就能鏈式處理程序。
Yield作為表達【我們開始說協程了~】:
grep.py

yield最重要的問題在於yield的值是多少。
yield的值需要使用coroutine協程這個概念 相對於僅僅生成值,函數可以動態處理傳送進去的值,而最後值通過yield返回。
協程的執行:
協程的執行和生成器的執行很相似。 當你初始化一個協程,不會返回任何東西。 協程隻能響應run和send函數。 協程的執行依賴run和send函數。 如果你缺誌同道合的朋友!缺學習Python的氛圍!缺入門資料和視頻!缺書籍的PDF!缺遇到問題沒人解答?那就加這個群:103456743 你想要一起學習的朋友?資料免費提供的學習環境?好比圖書館!不收你一分錢,隻為提供一個良好的交流平台!編程貴在多交流!
協程啟動:
所有的協程都需要調用.next( )函數。 調用的next( )函數將要執行到第一個yield表達式的位置。 在yield表達式的位置上,很容易去執行就可以。 協程使用next()啟動。
使用協程的修飾器:
由【協程啟動】中我們知道,啟動一個協程需要記得調用next( )來開始協程,而這個啟動器容易忘記使用。 使用修飾器包一層,來讓我們啟動協程。 【以後所有的協程器都會先有@coroutine

使用GeneratorExit這個異常類型
拋出一個異常:
在一個協程中,可以拋出一個異常



第三部分,管道過濾器:
叫過濾器其實並不貼切,應該叫中間人Intermediate:其兩端都是send()函數。

(協程的中間層) 典型的中間層如下:

協程和生成器的對比

不同處:生成器使用了迭代器拉取數據,協程使用send()壓入數據。
變得多分支:(上一個協程發送數據去多個下一段協程)
圖示:

使用協程,你可以發送數據 給 多個 協程過濾器/協程終了。但是請注意,協程源隻是用來傳遞數據的,過多的在協程源中傳遞數據是令人困惑並且複雜的。
一個例子

從文章中分別打印出含有’python‘ ’ply‘ ’swig‘ 關鍵字的句子。使用了一個協程隊列向所有printer協程 送出 接收到的數據。 圖示:

或者這樣Hook them up:

第三部分:協程,事件分發
事件處理
協程可以用在寫各種各樣處理事件流的組件。
介紹一個例子【這個例子會貫穿這個第三部分始終】要求做一個實時的公交車GPS位置監控。編寫程序的主要目的是處理一份文件。傳統上,使用SAX進行處理。【SAX處理可以減少內存空間的使用,但SAX事件驅動的特性會讓它笨重和低效】。
把SAX和協程組合在一起
我們可以使用協程分發SAX事件,比如:

【最終的組合】
比如,把xml改成json最後從中篩選的出固定信息. buses.py

協程的一個有趣的事情是,您可以將初始數據源推送到低級別的語言,而不需要重寫所有處理階段。比如,PPT 中69-73頁介紹的,可以通過協程和低級別的語言進行聯動,從而達成非常好的優化效果。如Expat模塊或者cxmlparse模塊。 ps: ElementTree具有快速的遞增xml句法分析
第四部分:從數據處理到並發編程
複習一下上麵學的特點:
協程有以下特點。
協程和生成器非常像。
我們可以用協程,去組合各種簡單的小組件。
我們可以使用創建進程管道,數據流圖的方法去處理數據。
你可以使用伴有複雜數據處理代碼的協程。
一個相似的主題:
我們往協程內傳送數據,向線程內傳送數據,也向進程內傳送數據。那麼,協程自然很容易和線程和分布式係統聯係起來。
基礎的並發:
我們可以通過添加一個額外的層,從而封裝協程進入線程或者子進程。這描繪了幾個基本的概念。

目標!協程+線程【沒有蛀牙。
下麵看一個線程的例子。 cothread.py

例子解析:第一部分:先新建一個隊列。然後定義一個永久循環的線程;這個線程可以將其中的元素拉出消息隊列,然後發送到目標裏麵。第二部分:接受上麵送來的元素,並通過隊列,將他們傳送進線程裏麵。其中用到了GeneratorExit ,使得線程可以正確的關閉。
Hook up:cothread.py


但是:添加線程讓這個例子慢了50%
目標!協程+子進程
我們知道,進程之間是不共享係統資源的,所以要進行兩個子進程之間的通信,我們需要通過一個文件橋接兩個協程。


程序通過sendto()和recvfrom()傳遞文件。
和環境結合的協程:
使用協程,我們可以從一個任務的執行環境中剝離出他的實現。並且,協程就是那個實現。執行環境是你選擇的線程,子進程,網絡等。
需要注意的警告 :
創建大量的協同程序,線程和進程可能是創建 不可維護 應用程序的一個好方法,並且會減慢你程序的速度。需要學習哪些是良好的使用協程的習慣。
在協程裏send()方法需要被適當的同步。
如果你對已經正在執行了的協程使用send()方法,那麼你的程序會發生崩潰。如:多個線程發送數據進入同一個協程。
同樣的不能創造循環的協程:

堆棧發送正在構建一種調用堆棧(send()函數不返回,直到目標產生)。
如果調用一個正在發送進程的協程,將會拋出一個錯誤。
send() 函數不會掛起任何一個協程的執行。
第五部分:任務一樣的協程
Task的概念
在並發編程中,通常將問題細分為“任務”。 “任務”有下麵幾個經典的特點: * 擁有獨立的控製流。 * 擁有內在的狀態。 * 可以被安排規劃/掛起/恢複。 * 可與其他的任務通信。 協程也是任務的一種。
協程是任務的一種:
下麵的部分 來告訴你協程有他自己的控製流,這裏 if 的控製就是控製流。


需要解決的問題(還在複習微嵌知識)
CPU執行的是應用程序,而不是你的操作係統,那 沒有被CPU執行的操作係統 是怎麼控製 正在運行的應用程序 中斷的呢。
中斷(interrupts)和陷阱(Traps)
操作係統隻能通過兩個機製去獲得對應用程序的控製:中斷和陷阱。 * 中斷:和硬件有關的balabala。 * 陷阱:一個軟件發出的信號。 在兩種狀況下,CPU都會掛起正在做的,然後執行OS的代碼(這個時候,OS的代碼成功插入了應用程序的執行),此時,OS來切換了程序。
中斷的底層實現(略…碼字員微嵌隻有70分��♀️)
中斷的高級表現:
* 中斷(Traps)使得OS的代碼可以實現。* 在程序運行遇到中斷(Traps)時,OS強製在CPU上停止你的程序。* 程序掛起,然後OS運行。
表現如下圖:

每次中斷(Traps)程序都會執行另一個不同的任務。
任務調度(非常簡單):
為了執行很多任務,添加一簇任務隊列。

啟示(很重要):
BB了這麼多微嵌的內容,得到的是什麼結論呢。類比任務調度,協程中yield聲明可以理解為中斷(Traps)。當一個生成器函數碰到了yield聲明,那函數將立即掛起。而執行被傳給生成器函數運行的任何代碼。如果你把yield聲明看成了一個中斷,那麼你就可以組件一個多任務執行的操作係統了。
第七部分:讓我們建一個操作係統。【起飛了,請握好扶手
目標:滿足以下條件建成一個操作係統。
1. 用純python語句。2. 不用線程。3. 不用子進程。4. 使用生成器和協程器。
我們用python去構建操作係統的一些動機:
* 尤其在存在線程鎖(GIL)的條件下,在線程間切換會變得非常重要。我要高並發!* 不阻塞和異步I/O。我要高並發!* 在實戰中可能會遇到:服務器要同時處理上千條客戶端的連接。我要高並發!* 大量的工作 致力於實現 事件驅動 或者說 響應式模型。我要組件化!* 綜上,python構建操作係統,有利於了解現在高並發,組件化的趨勢。
第一步:定義任務
定義一個任務類:任務像一個協程的殼,協程函數傳入target;任務類僅僅有一個run()函數。 pyos1.py

在foo中,yield就像中斷(Traps)一樣,每次執行run(),任務就會執行到下一個yield(一個中斷)。
第二步:構建調度者
下麵是調度者類,兩個屬性分別是Task隊列和task_id與Task類對應的map。schedule()向隊列裏麵添加Task。new()用來初始化目標函數(協程函數),將目標函數包裝在Task,進而裝入Scheduler。最後mainloop會從隊列裏麵拉出task然後執行到task的target函數的yield為止,執行完以後再把task放回隊列。這樣下一次會從下一個yield開始執行。 pyos2.py


第三步:確定任務的停止條件
如果,target函數裏麵不是死循環,那麼上麵的代碼就會出錯。所以我們對Scheduler做改進。添加一個從任務隊列中刪除的操作,和對於StopIteration的驗證。 【對scheduler做改進的原因是任務的性質:可以被安排規劃/掛起/恢複。】

第四步:添加係統調用基類。
在OS中,中斷是應用程序請求係統服務的方式。在我們的代碼中,OS是調度者(scheduler),而中斷是yield。為了請求調度者服務,任務需要帶值使用yield聲明。 pyos4.py

代碼解析: 1. 如果taskmap裏麵存在task,就從ready隊列裏麵拿任務出來,如果沒有就結束mainloop。 2. 【就是傳說中的係統調運部分】ready隊列裏麵的task被拿出來以後,執行task,返回一個result對象,並初始化這個result對象。如果隊列裏麵的task要停止迭代了(終止yield這個過程)就從隊列裏刪除這個任務。 3. 最後再通過schedule函數把執行後的task放回隊列裏麵。 4. 係統調用基類,之後所有的係統調用都要從這個基類繼承。
第4.5步:添加第一個係統調用
這個係統調用想返回任務的id。 Task的sendval屬性就像一個係統調用的返回值。當task重新運行的是後,sendval將會傳入這個係統調用。 pyos4.py

理解這段代碼的前提: (非常重要) 1. send()函數有返回值的,返回值是yield表達式右邊的值。在本段代碼中,result的返回值是yield GetTid()的GetTid的實例或者是yield後麵的。 2. 執行send(sendval)以後,sendval被傳入了yield表達式。並賦給了mytid,返回GetTid()給ruselt。
執行順序: 先創建一個調度者(Scheduler),然後在調度者裏麵添加兩個協程函數:foo(), bar(),最後觸發mainloop進行協程的調度執行。
係統調用原理: 係統調用是基於係統調用類實現的,如GetTid類,其目的是傳出自己的tid。傳出自己的tid之後,再將task放回隊列。
第五步:任務管理
上麵我們搞定了一個GetTid係統調用。我們現在搞定更多的係統調用: * 創建一個新的任務。 * 殺掉一個已經存在的任務。 * 等待一個任務結束。 這些細小的相同的操作會與線程,進程配合。
1. *創建一個新的係統調用*:通過係統調用加入一個task。




網絡服務器的搭建:
現在已經完成了: * 多任務。 * 開啟新的進程。 * 進行新任務的管理。 這些特點都非常符合一個web服務器的各種特點。下麵做一個Echo Server的嚐試。

但問題是這個網絡服務器是I / O阻塞的。整個python的解釋器需要掛起,一直到I/O操作結束。
非阻塞的I/O
先額外介紹一個叫Select的模塊。select模塊可以用來監視一組socket鏈接的活躍狀態。用法如下:


源碼解析:__init__裏麵的是兩個字典。用來存儲阻塞的IO的任務。waitforread()和waitforwrite()將需要等待寫入和等待讀取的task放在dict裏麵。這裏的iopoll():使用select()去決定使用哪個文件描述器,並且能夠不阻塞任意一個和I/O才做有關係的任務。poll這個東西也可以放在mainloop裏麵,但是這樣會帶來線性的開銷增長。 詳情請見: Python Select 解析 - 金角大王 - 博客園
添加新的係統調用:

更多請見開頭那個連接裏麵的代碼:pyos8.py
這樣我們就完成了一個多任務處理的OS。這個OS可以並發執行,可以創建、銷毀、等待任務。任務可以進行I/O操作。並且最後我們實現了並發服務器。
第八部分:協程棧的一些問題的研究。
我們可能在使用yield的時候會遇到一些問題:

讓我們來看看這個叫蹦床的奇淫巧技。
代碼:trampoline.py

整個控製流如下:

我們看到,上圖中左側為統一的scheduler,如果我們想調用一個子線程,我們都用通過上麵的scheduler進行調度。

千萬不要一個函數裏麵包含兩個或多個以上的功能,比如函數是generator就是generator,是一個coroutine就是一個coroutin。
謝謝閱讀!

如有侵權請聯係小編刪除哦!
相關資訊
最新熱門應用
芝麻交易所最新版本
其它軟件223.89MB
下載
bione數字貨幣交易所5.1.9最新版
其它軟件49.33M
下載
zb交易所手機app
其它軟件225.08MB
下載
ght交易平台
其它軟件168.21M
下載
芝麻交易所ios蘋果版
其它軟件223.89MB
下載
zt交易所包
其它軟件273.2 MB
下載
ubcoin交易所官網
其它軟件18.21MB
下載
mxc官方交易平台app
其它軟件84.30MB
下載
鏈易交易平台app
其它軟件72.70MB
下載
mxc交易所app
其它軟件98.2MB
下載