中文字幕在线观看,亚洲а∨天堂久久精品9966,亚洲成a人片在线观看你懂的,亚洲av成人片无码网站,亚洲国产精品无码久久久五月天

在Python中按需處理數(shù)據(jù),第3部分: 協(xié)程和 asyncio

2018-08-07    來源:raincent

容器云強勢上線!快速搭建集群,上萬Linux鏡像隨意使用
在本系列的第 1 部分中,您了解了 Python 迭代器;在第 2 部分中,您了解了 itertools。在這一部分,將了解一種稱為協(xié)程(Coroutines)的特殊的生成器函數(shù)。您還將了解另一個功能強大但十分復雜的標準庫模塊:asyncio。

想象您走進一家小餐館。餐館里有 3 張桌子,且僅有一位服務員。您知道將會發(fā)生什么。服務員為您遞上菜單,然后回來取走您的訂單。在廚師準備好您訂購的食物后,服務員會將其端給您。用完餐后,服務員會將賬單帶給您,并在您準備好付款時回到您的桌邊。

其他桌的客人正在愉快地用餐,因為當您考慮點什么菜時,或者在廚師準備您的食物時,抑或在您用餐時,服務員可以對其他桌的客人執(zhí)行這些步驟。當多個桌子的客人同時需要服務員的關(guān)注時,您可能需要等幾分鐘,但等待時間不會太長。

如果只有一桌客人,從客人走進餐館到離開餐館平均需要 1 小時的時間;如果還有另一桌客人,可能需要花費 1 小時 10 分鐘的時間;如果其他兩桌都有客人,則會花費 1 小時 20 分鐘的時間。這種情況不算太糟糕。

現(xiàn)在假設(shè)您走進了餐館,但服務員只關(guān)注一桌客人,直到他們完成所有的步驟。您甚至可能需要等待兩個小時后才開始用餐,因為服務員率先處理了其他桌客人的用餐。這不會是一家受歡迎的餐館。
 

同步與異步


令人驚訝的是,我們編寫的許多計算機代碼的工作方式類似于這家效率極低的餐館。在計算機術(shù)語中,不受歡迎的餐館情形稱為串行操作,服務員的操作稱為同步操作。我們習慣的餐館情形是,服務員可以同時關(guān)注不同桌子的客人,滿足他們的需求,這種情形稱為并行操作,服務員的操作稱為異步操作。

我在這個類比上花這么多時間的原因是,它闡明了一項最重要的技術(shù),開發(fā)人員應該適當學習這項技術(shù),以便編寫能夠使用以輸入/輸出 (I/O) 為基礎(chǔ)的數(shù)據(jù)庫、網(wǎng)絡(luò)和其他這類資源的可擴展應用程序,F(xiàn)實世界的餐館之所以使用異步流程,是因為如果不這樣做,它們就不會有吸引力,也不會有競爭力。理想情況下,實際程序傾向于使用異步流程,但開發(fā)人員需要利用正確的工具、庫、技能和實踐才能實現(xiàn)異步流程。本教程是教程系列的第 3 部分,將介紹如何在 Python 中這么做。

我想說的是,Python 支持異步編程的工具紛繁復雜,有許多不同的方法來實現(xiàn)異步編程。這些工具中有些是最近才添加的,有些輔助功能尚處于試驗階段。不過,這個主題很重要,值得堅持。我會通過這些工具中的一個實用子集,有意識地引導您熟悉基本概念,然后您就可以自行探索其他方法。
 

協(xié)程


您在前面的教程中了解了生成器函數(shù),以及它們與常規(guī)函數(shù)的區(qū)別。當調(diào)用者調(diào)用常規(guī)函數(shù)時,流程是從頂部開始的,并根據(jù)函數(shù)的邏輯從某個位置退出。借助生成器,調(diào)用者可以多次進出單個函數(shù),暫停并恢復其執(zhí)行。

可以多次進出、每次暫停并恢復執(zhí)行的函數(shù)被稱為協(xié)程。生成器只是一種簡化的協(xié)程。Python 有多種類型的協(xié)程,但本教程的重點是設(shè)計用來支持異步編程的協(xié)程類型。讓我們回到餐館的類比上。每桌的菜單/下單/用餐/結(jié)賬/付款步驟是一個單獨的協(xié)程,但服務員會暫停并重新關(guān)注每桌的客人,以便所有 3 個協(xié)程能同時運行(可能處于流程的不同階段)。服務員訓練有素的大腦充當著處理這些并行協(xié)程的調(diào)度程序。

在同步餐館中,所有操作都是常規(guī)函數(shù)。在客人到達時,進入函數(shù)一次;在客人離開時,退出函數(shù)一次。一次只能運行一個這樣的函數(shù),所以客人可能需要等上兩個小時才能開始自己的用餐體驗。

在異步餐館中,會在客人到達時首次進入?yún)f(xié)程函數(shù),創(chuàng)建一個協(xié)程對象,并在客人離開時最終退出函數(shù),此時不再需要協(xié)程對象。但是,在服務員提供菜單后,他們可以暫停這桌客人的協(xié)程對象,檢查是否需要關(guān)注其他任何桌子的客人。在任何指定桌子的客人執(zhí)行下單、結(jié)賬等步驟后,服務員也會執(zhí)行同樣的操作。
 

餐館服務員代碼


利用 Python 的可讀性幾乎與偽代碼一樣的事實,下面給出了服務員協(xié)程的一個實際實現(xiàn)。



此函數(shù)是使用 async def 而不只是使用 def 定義的。這會將它標記為異步協(xié)程函數(shù)。順便提一下,還有一些異步協(xié)程生成器函數(shù),其主體中的某處有一個 yield 語句,但這些都是特殊情況,超出了本教程系列的討論范疇。老實說,Python 3 中眾多的函數(shù)/生成器/協(xié)程類型讓人眼花繚亂,但本教程系列會忽略一些可能情況,僅提供一個簡單的入門途徑。

serve_table 的主體中包含一系列 await 語句。這會從調(diào)用的協(xié)程函數(shù)創(chuàng)建一個協(xié)程對象并調(diào)用此對象,同時將控制權(quán)轉(zhuǎn)交給其他任何準備運行的協(xié)程。這相當于餐館服務員啟動了一個流程,比如讓廚師開始準備食物,同時檢查是否有任何其他桌的客人需要關(guān)注。

對任務的這種處理發(fā)生在訓練有素的服務員的大腦中,在 Python 中,類似情況稱為事件循環(huán)。我們稍后再介紹事件循環(huán)。
 

更多協(xié)程


讓我們看看 serve_table 調(diào)用的其他協(xié)程的實現(xiàn)。



這些函數(shù)使用一個睡眠計時器來模擬如何花時間做一些處理。random.randrange 函數(shù)提供了一個整數(shù)范圍,用于從中隨機挑選一個整數(shù)。asyncio.sleep 函數(shù)是一個特殊的協(xié)程,它會在給定的秒數(shù)內(nèi)暫停操作。當然,在此睡眠期間,事件循環(huán)可以自由運行其他任何準備好的協(xié)程。像往常一樣,您可以使用 await 關(guān)鍵字調(diào)用此函數(shù)。

此時順便提一下,您只能在異步協(xié)程函數(shù)(比如使用 async def 定義的函數(shù))的主體中使用 await 關(guān)鍵字。在其他任何地方使用 await 都是一個語法錯誤。

請注意,get_order 協(xié)程返回了一個值。此值是在調(diào)用者的 await 語句中傳回的。
 

一步到位:事件循環(huán)


我之前提到過事件循環(huán)。您需要使用一些特殊的設(shè)置代碼來進入異步模式,創(chuàng)建一個事件循環(huán)來調(diào)度和管理各個協(xié)程,就像您將它們編碼為協(xié)作任務一樣。asyncio 協(xié)程也可簡化稱為任務。當某個協(xié)程使用 await 將控制權(quán)轉(zhuǎn)交給另一個協(xié)程時,它實際上是將控制權(quán)轉(zhuǎn)交回事件循環(huán)。事件循環(huán)就像服務員訓練有素的大腦。

以下是運行我們目前為止定義的餐館服務員協(xié)程的代碼。



特殊協(xié)程 asyncio.gather 采用一個或多個其他協(xié)程,安排它們?nèi)窟\行,且在所有集合協(xié)程都運行完才算完成。這里使用它在事件循環(huán)中運行 3 個桌子的協(xié)程,我們已經(jīng)先使用 asyncio.get_event_loop 獲得了事件循環(huán)。下一行代碼運行了給定的協(xié)程,直至它完成。由于向該協(xié)程傳遞了收集的 3 個協(xié)程,因此在所有 3 個協(xié)程都完成后它才結(jié)束運行。當然,每個 serve_table 協(xié)程都會調(diào)用其他協(xié)程,比如使用 await 調(diào)用 get_menusget_order,然后使用事件循環(huán)來調(diào)度這些協(xié)程。
 

完整程序


清單 1. serve_tables.py 是完整的程序

這是運行此程序的輸出示例。



請注意,大部分行之間都會有幾秒的延遲。這是各個協(xié)程中的睡眠延遲,它模擬了在餐館中處理事務所花費的時間。時間被壓縮,程序中的一秒表示餐館中的一分鐘。由于睡眠延遲時長是隨機的,所以每次運行程序時,消息的出現(xiàn)順序都是不同的。

另請注意,該程序并不總是完全從 1 號桌開始,然后是 2 號桌、3 號桌。asyncio.gather 協(xié)程會調(diào)度您為它提供的協(xié)程,但沒有特定的順序。

這里要注意的主要事情是協(xié)作式多任務的流程。研究上面的完整清單,同時運行并調(diào)整代碼,直到您完全了解協(xié)程如何釋放和重獲控制權(quán)。有時,所有 3 個 serve_table 協(xié)程對象都在調(diào)用其他某個協(xié)程,所有協(xié)程都在等待睡眠延遲。在這幾秒內(nèi),您看不到任何輸出。在這些時刻,事件循環(huán)會耐心地檢查每個協(xié)程,查看它們何時可以恢復執(zhí)行。
 

添加協(xié)程


我提到了如何獲得在運行清單 1 中程序獲得的輸出之間的延遲。顯示某種進度指示器會更加方便用戶的使用。您可以使用協(xié)作式多任務的強大功能來實現(xiàn)此目的。下面的協(xié)程函數(shù)每秒顯示一個點兩次,以此作為進度指示器。



此函數(shù)采用了兩個參數(shù):打印點的最短延遲和事件循環(huán)對象。例如,在清單 1 底部附近創(chuàng)建的就是這種對象。為了確保一組受控協(xié)程之間能夠保持協(xié)作,您需要將一個循環(huán)對象傳遞給許多 asyncio。在本例中,將事件循環(huán)傳遞給了 asyncio.Task.all_tasks,然后,后者返回所有在該事件循環(huán)中調(diào)度的任務(即協(xié)程)的列表,包括那些已完成的任務。為了僅獲得未完成的任務,請使用 task.done 進一步篩選該列表。

假設(shè)您利用此函數(shù)創(chuàng)建了一個協(xié)程對象,并傳入 0.5 作為延遲。它會直接進入一個無限循環(huán),就像您可能還記得的之前教程中的無限生成器那樣。然后,它會調(diào)用睡眠延遲,但在外部實體取消該協(xié)程(可以通過多種方式)時,會出現(xiàn)異常。在這些情況下,協(xié)程會中斷并拋出 asyncio.CancelledError 異常,導致我們跳出無限循環(huán)。

通常,協(xié)程恢復正常后,它會輸出一個點,然后檢查其他所有協(xié)程是否在正常運行。如果 progress_indicator 是唯一剩下的協(xié)程,它會跳出無限循環(huán)。
 

清單 2.為了使用 progress_indicator 協(xié)程而更新的完整清單

請注意,現(xiàn)在,在創(chuàng)建協(xié)程集合之前,事件循環(huán)已經(jīng)出現(xiàn)了。這是因為必須將此循環(huán)傳遞給 progress_indicator,正如您在要收集的協(xié)程列表中看到的那樣。

下面的輸出來自一個樣本運行:


 .

進度指示器的點會有規(guī)律地出現(xiàn),約半秒出現(xiàn)一個。
 

這是何種類型的多任務?


如果您在 Python 中實現(xiàn)過多線程或多處理,您可能想知道它們與這種 asyncio 協(xié)作式多任務方法有何不同。主要區(qū)別在于,在 asyncio 方法中,不會實際嘗試讓兩個協(xié)程在同一時間做一些事情,就像餐館服務員無法在為 3 號桌上餐的同時為 1 號桌提供菜單一樣。asyncio 事件循環(huán)所做的是利用任務內(nèi)的自然停機時間,允許協(xié)程在有工作要做時執(zhí)行工作,但在其他協(xié)程空閑時將控制權(quán)轉(zhuǎn)交給它們。

協(xié)程無法控制它再次運行的時間,此方法稱為協(xié)作式多任務是有原因的。如果某個協(xié)程花了太長時間而沒有將控制權(quán)轉(zhuǎn)交回事件循環(huán),它會阻塞一切操作,導致不必要的延遲,而且您將失去多任務的優(yōu)勢。這意味著您首先必須確保您的程序適合用這種方式實現(xiàn),然后必須小心地編寫程序代碼,將它分解為會在恰當時機相互釋放控制權(quán)的協(xié)程。這可能比聽起來還要復雜,因為您可能無意識地從某個協(xié)程調(diào)用常規(guī)函數(shù),這會花費很長時間,而且問題不會很明顯。

一般來說,asyncio 事件循環(huán)最適合經(jīng)常聯(lián)網(wǎng)的程序,或大量查詢數(shù)據(jù)庫和類似對象的程序。等待遠程服務器或數(shù)據(jù)庫響應請求或查詢時,是將控制權(quán)釋放給事件循環(huán)的理想時刻。在過去,程序員傾向于在這種情況下使用線程,但與多線程相比,asyncio 事件循環(huán)是一種更加清晰和靈活的編程方式。一個難點是,要充分發(fā)揮 asyncio 事件循環(huán)的優(yōu)勢,需要在 asyncio 協(xié)程中對網(wǎng)絡(luò)和數(shù)據(jù)庫 API 進行編碼。幸運的是,現(xiàn)在許多 Python 第三方庫已實現(xiàn)了對 asyncio 的充分利用。

不過,有時您可能會遇到這樣的情況:您想要使用 asyncio,但需要使用不支持 asyncio 的庫。換言之,您需要從異步代碼中調(diào)用同步代碼,而不破壞多線程?梢允褂迷趩为毜木程或流程中運行同步代碼的 asyncio 執(zhí)行器實現(xiàn)此目的。我提到這一點是因為您可能想了解這一點,但更多的細節(jié)超出了這些教程的討論范疇。
 

結(jié)束語


隨著您越來越精通 asyncio,您會了解到與該技術(shù)相關(guān)的其他外來概念,包括令人印象深刻的“future”。您還會了解到,協(xié)程可通過不同方式將控制權(quán)釋放給事件循環(huán),包括 async with,如果您使用的是 Python 3.6 或更高版本,還包括 async for。由于本教程系列要求的最低版本是 Python 3.5,所以我不會討論后者,但在下一篇教程中,您將學習 async with,以及其他很酷的技術(shù)。

標簽: 代碼 服務器 數(shù)據(jù)庫 網(wǎng)絡(luò)

版權(quán)申明:本站文章部分自網(wǎng)絡(luò),如有侵權(quán),請聯(lián)系:west999com@outlook.com
特別注意:本站所有轉(zhuǎn)載文章言論不代表本站觀點!
本站所提供的圖片等素材,版權(quán)歸原作者所有,如需使用,請與原作者聯(lián)系。

上一篇:數(shù)據(jù)中心如何從模塊化走向智能化?

下一篇:對比美英與我國數(shù)據(jù)科學教育戰(zhàn)略、現(xiàn)狀