溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Python同步與異步有什么不同

發(fā)布時間:2022-02-21 15:57:05 來源:億速云 閱讀:107 作者:iii 欄目:開發(fā)技術

本文小編為大家詳細介紹“Python同步與異步有什么不同”,內容詳細,步驟清晰,細節(jié)處理妥當,希望這篇“Python同步與異步有什么不同”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學習新知識吧。

“同步”和“異步”是什么意思?

Web 應用程序通常要處理許多請求,這些請求在短時間內來自不同的客戶端。為避免處理延遲,必須考慮并行處理多個請求,這通常稱為“并發(fā)”。

在本文中,我將繼續(xù)使用 Web 應用程序作為例子,但還有其它類型的應用程序也從并發(fā)中獲益。因此,這個討論并不僅僅是針對 Web 應用程序的。

術語“同步”和“異步”指的是編寫并發(fā)應用程序的兩種方式。所謂的“同步”服務器使用底層操作系統(tǒng)支持的線程和進程來實現(xiàn)這種并發(fā)性。

在這種情況下,我們有 5 臺客戶端,都向應用程序發(fā)送請求。這個應用程序的訪問入口是一個 Web 服務器,通過將服務分配給一個服務器 worker 池來充當負載均衡器,這些 worker 可以實現(xiàn)為進程、線程或者兩者的結合。這些 worker 執(zhí)行負載均衡器分配給他們的請求。你使用 Web 應用程序框架(例如 Flask 或 Django)編寫的應用程序邏輯運行在這些 worker 中。

這種類型的方案對于有多個 CPU 的服務器比較好,因為你可以將 worker 的數(shù)量設置為 CPU 的數(shù)量,這樣你就能均衡地利用你的處理器核心,而單個 Python 進程由于全局解釋器鎖(GIL)的限制無法實現(xiàn)這一點。

在缺點上,上面的示意圖也清楚展示了這種方案的主要局限。我們有 5 個客戶端,卻只有 4 個 worker。如果這 5 個客戶端在同一時間都發(fā)送請求,那么負載均衡器會將某一個客戶端之外的所有請求發(fā)送到 worker 池,而剩下的請求不得不保留在一個隊列中,等待有 worker 變得可用。因此,五分之四的請求會立即響應,而剩下的五分之一需要等一會兒。服務器優(yōu)化的一個關鍵就在于選擇適當數(shù)量的 worker 來防止或最小化給定預期負載的請求阻塞。

這種類型的服務器運行在單個進程中,通過循環(huán)控制。這個循環(huán)是一個非常有效率的任務管理器和調度器,創(chuàng)建任務來執(zhí)行由客戶端發(fā)送的請求。與長期存在的服務器 worker 不同,異步任務是由循環(huán)創(chuàng)建,用來處理某個特定的請求,當那個請求完成時,該任務也會被銷毀。任何時候,一臺異步服務器都會有上百或上千個活躍的任務,它們都在循環(huán)的管理下執(zhí)行自己的工作。

你可能想知道異步任務之間的并行是如何實現(xiàn)的。這就是有趣的部分,因為一個異步應用程序通過唯一的協(xié)同多任務處理來實現(xiàn)這點。這意味著什么?當一個任務需要等待一個外部事件(例如,一個數(shù)據(jù)庫服務器的響應)時,不會像一個同步的 worker 那樣等待,而是會告訴循環(huán),它需要等待什么,然后將控制權返回給它。循環(huán)就能夠在這個任務被數(shù)據(jù)庫阻塞的時候發(fā)現(xiàn)另外一個準備就緒的任務。最終,數(shù)據(jù)庫將發(fā)送一個響應,而那時循環(huán)會認為第一個的任務已經(jīng)準備好再次運行,并將盡快恢復它。

異步任務暫停和恢復執(zhí)行的這種能力可能在抽象上很難理解。為了幫你應用到你已經(jīng)知道的東西,可以考慮在 Python 中使用awaityield關鍵字這一方法來實現(xiàn),但你之后會發(fā)現(xiàn),這并不是唯一實現(xiàn)異步任務的方法。

一個異步應用程序完全運行在單個進程或線程中,這可以說是令人吃驚的。當然,這種類型的并發(fā)需要遵循一些規(guī)則,因此,你不能讓一個任務占用 CPU 太長時間,否則,剩余的任務會被阻塞。為了異步執(zhí)行,所有的任務需要定時主動暫停并將控制權返還給循環(huán)。為了從異步方式獲益,一個應用程序需要有經(jīng)常被 I/O 阻塞的任務,并且沒有太多 CPU 工作。Web 應用程序通常非常適合,特別是當它們需要處理大量客戶端請求時。

在使用一個異步服務器時,為了最大化多 CPU 的利用率,通常需要創(chuàng)建一個混合方案,增加一個負載均衡器并在每個 CPU 上運行一個異步服務器。

Python 中實現(xiàn)異步的 2 種方法

我敢肯定,你知道要在 Python 中寫一個異步應用程序,你可以使用 asyncio package,這個包是在協(xié)程的基礎上實現(xiàn)了所有異步應用程序都需要的暫停和恢復特性。其中yield關鍵字,以及更新的asyncawait都是asyncio構建異步能力的基礎。

https://docs.python.org/3/library/asyncio.html

Python 生態(tài)系統(tǒng)中還有其它基于協(xié)程的異步方案,例如 Trio 和 Curio。還有 Twisted,它是所有協(xié)程框架中最古老的,甚至出現(xiàn)得比asyncio都要早。

如果你對編寫異步 Web 應用程序感興趣,有許多基于協(xié)程的異步框架可以選擇,包括 aiohttp、sanic、FastAPI 和 Tornado。

很多人不知道的是,協(xié)程只是 Python 中編寫異步代碼的兩種方法之一。第二種方法是基于一個叫做 greenlet 的庫,你可以用 pip 安裝它。Greenlets 和協(xié)程類似,它們也允許一個 Python 函數(shù)暫停執(zhí)行并稍后恢復,但是它們實現(xiàn)這點的方式完全不同,這意味著 Python 中的異步生態(tài)系統(tǒng)分成兩大類。

協(xié)程與 greenlets 之間針對異步開發(fā)最有意思的區(qū)別是,前者需要 Python 語言特定的關鍵字和特性才能工作,而后者并不需要。我的意思是,基于協(xié)程的應用程序需要使用一種特定的語法來書寫,而基于 greenlet 的應用程序看起來幾乎和普通 Python 代碼一樣。這非???,因為在某些情況下,這讓同步代碼可以被異步執(zhí)行,這是諸如asyncio之類的基于協(xié)程的方案做不到的。

那么在 greenlet 方面,跟asyncio對等的庫有哪些?我知道 3 個基于 greenlet 的異步包:Gevent、Eventlet 和 Meinheld,盡管最后一個更像是一個 Web 服務器而不是一個通用的異步庫。它們都有自己的異步循環(huán)實現(xiàn),而且它們都提供了一個有趣的“monkey-patching”功能,取代了 Python 標準庫中的阻塞函數(shù),例如那些執(zhí)行網(wǎng)絡和線程的函數(shù),并基于 greenlets 實現(xiàn)了等效的非阻塞版本。如果你有一些同步代碼想要異步運行,這些包會對你有所幫助。

據(jù)我所知,唯一明確支持 greenlet 的 Web 框架只有 Flask。這個框架會自動監(jiān)測,當你想要運行在一個 greenlet Web 服務器上時,它會自我進行相應調整,而無需進行任何配置。這么做時,你需要注意不要調用阻塞函數(shù),或者,如果你要調用阻塞函數(shù),最好用猴子補丁來“修復”那些阻塞函數(shù)。

但是,F(xiàn)lask 并不是唯一受益于 greenlets 的框架。其它 Web 框架,例如 Django 和 Bottle],雖然沒有 greenlets,但也可以通過結合一個 greenlet Web 服務器并使用 monkey-patching 修復阻塞函數(shù)的方式來異步運行。

異步比同步更快嗎?

對于同步和異步應用程序的性能,存在著一個廣泛的誤解——異步應用程序比同步應用程序快得多。

對此,我需要澄清一下。無論是用同步方式寫,還是用異步方式寫,Python 代碼運行速度是幾乎相同的。除了代碼,有兩個因素能夠影響一個并發(fā)應用程序的性能:上下文切換和可擴展性。

上下文切換

在所有運行的任務間公平地共享 CPU 所需的工作,稱為上下文切換,能夠影響應用程序的性能。對同步應用程序來說,這項工作是由操作系統(tǒng)完成的,而且基本上是一個黑箱,不需要配置或微調選項。對異步應用程序來說,上下文切換是由循環(huán)完成的。

默認的循環(huán)實現(xiàn)由asyncio提供,是用 Python 編寫的,效率不是很高。而 uvloop 包提供了一個備選的循環(huán)方案,其中部分代碼是用 C 編寫的來實現(xiàn)更好的性能。Gevent 和 Meinheld 所使用的事件循環(huán)也是用 C 編寫的。Eventlet 用的是 Python 編寫的循環(huán)。

高度優(yōu)化的異步循環(huán)比操作系統(tǒng)在進行上下文切換方面更有效率,但根據(jù)我的經(jīng)驗,要想看到實際的效率提升,你運行的并發(fā)量必須非常大。對于大部分應用程序,我不認為同步和異步上下文切換之間的性能差距有多明顯。

擴展性

我認為異步更快這個神話的來源是,異步應用程序通常會更有效地使用 CPU、能更好地進行擴展并且擴展方式比同步更靈活。

如果上面示意圖中的同步服務器同時收到 100 個請求,想一下會發(fā)生什么。這個服務器同時最多只能處理 4 個請求,因此大部分請求會停留在一個隊列中等待,直到它們被分配一個 worker。

與之形成對比的是,異步服務器會立即創(chuàng)建 100 個任務(或者使用混合模式的話,在 4 個異步 worker 上每個創(chuàng)建 25 個任務)。使用異步服務器,所有請求都會立即開始處理而不用等待(盡管公平地說,這種方案也還會有其它瓶頸會減慢速度,例如對活躍的數(shù)據(jù)庫連接的限制)。

如果這 100 個任務主要使用 CPU,那么同步和異步方案會有相似的性能,因為每個 CPU 運行的速度是固定的,Python 執(zhí)行代碼的速度總是相同的,應用程序要完成的工作也是相同的。但是,如果這些任務需要做很多 I/O 操作,那么同步服務器只能處理 4 個并發(fā)請求而不能實現(xiàn) CPU 的高利用率。而另一方面,異步服務器會更好地保持 CPU 繁忙,因為它是并行地運行所有這 100 個請求。

你可能會想,為什么你不能運行 100 個同步 worker,那樣,這兩個服務器就會有相同的并發(fā)能力。要注意,每個 worker 需要自己的 Python 解釋器以及與之相關聯(lián)的所有資源,再加上一份單獨的應用程序拷貝及其資源。你的服務器和應用程序的大小將決定你可以運行多少個 worker 實例,但通常這個數(shù)字不會很大。另一方面,異步任務非常輕量,都運行在單個 worker 進程的上下文中,因此具有明顯優(yōu)勢。

綜上所述,只有如下場景時,我們可以說異步可能比同步快:

  • 存在高負載(沒有高負載,訪問的高并發(fā)性就沒有優(yōu)勢)

  • 任務是 I/O 綁定的(如果任務是 CPU 綁定的,那么超過 CPU 數(shù)目的并發(fā)并沒有幫助)

  • 你查看單位時間內的平均請求處理數(shù)。如果你查看單個請求的處理時間,你不會看到有很大差別,甚至異步可能更慢,因為異步有更多并發(fā)的任務在爭奪 CPU。

讀到這里,這篇“Python同步與異步有什么不同”文章已經(jīng)介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領會,如果想了解更多相關內容的文章,歡迎關注億速云行業(yè)資訊頻道。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內容。

AI