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

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

2018-08-07    來源:raincent

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

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

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

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

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

同步與異步


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

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

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

協(xié)程


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

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

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

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

餐館服務(wù)員代碼


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



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

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

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

更多協(xié)程


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



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

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

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

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


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

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



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

完整程序


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

這是運(yùn)行此程序的輸出示例。



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

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

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

添加協(xié)程


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



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

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

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

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

請(qǐng)注意,現(xiàn)在,在創(chuàng)建協(xié)程集合之前,事件循環(huán)已經(jīng)出現(xiàn)了。這是因?yàn)楸仨殞⒋搜h(huán)傳遞給 progress_indicator,正如您在要收集的協(xié)程列表中看到的那樣。

下面的輸出來自一個(gè)樣本運(yùn)行:


 .

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

這是何種類型的多任務(wù)?


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

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

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

不過,有時(shí)您可能會(huì)遇到這樣的情況:您想要使用 asyncio,但需要使用不支持 asyncio 的庫。換言之,您需要從異步代碼中調(diào)用同步代碼,而不破壞多線程。可以使用在單獨(dú)的線程或流程中運(yùn)行同步代碼的 asyncio 執(zhí)行器實(shí)現(xiàn)此目的。我提到這一點(diǎn)是因?yàn)槟赡芟肓私膺@一點(diǎn),但更多的細(xì)節(jié)超出了這些教程的討論范疇。
 

結(jié)束語


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

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

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

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

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