同步與異步Python有何不同?

2020-09-27 InfoQ

你是否聽到人們說過,異步 Python 代碼比「普通(或同步)Python 代碼更快?果真是那樣嗎?

「同步」和「異步」是什麼意思?

Web 應用程式通常要處理許多請求,這些請求在很短的時間段內來自不同的客戶端。為避免處理延遲,必須考慮並行處理多個請求,這通常稱為「並發」。

在本文中,我將繼續使用 Web 應用程式作為例子,但是要記住還有其它類型的應用程式也從並發完成多個任務中獲益,因此這個討論並不僅僅是針對 Web 應用程式的。

術語「同步」和「異步」指的是編寫並發應用程式的兩種方式。所謂的「同步」伺服器使用底層作業系統支持的線程和進程來實現這種並發性。下面是同步部署的一個示意圖:

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

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

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

一個異步伺服器的配置很難畫,但是我會盡力而為:

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

你可能想知道異步任務之間的並行是如何實現的。這就是有趣的部分,因為一個異步應用程式通過唯一的協同多任務處理來實現這點。這意味著什麼?當一個任務需要等待一個外部事件(例如,一個資料庫伺服器的響應)時,不會像一個同步的 worker 那樣等待,而是會告訴循環它需要等待什麼,然後將控制權返回給它。循環就能夠在這個任務被資料庫阻塞的時候發現另外一個準備就緒的任務。最終,資料庫將發送一個響應,而那時循環會認為第一個的任務已經準備好再次運行,並將儘快恢復它。

異步任務暫停和恢復執行的這種能力可能在抽象上很難理解。為了幫助你應用到你已經知道的東西,可以考慮在 Python 中使用await或yield關鍵字這一方法來實現,但你之後會發現這並不是唯一實現異步任務的方法。

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

在使用一個異步伺服器時,為了最大化多 CPU 的利用率,通常需要創建一個混合方案,增加一個負載均衡器並在每個 CPU 上運行一個異步伺服器,如下圖所示:

Python 中實現異步的 2 種方法

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

Python 生態系統中還有其它基於協程的異步方案,例如 Trio 和 Curio 。還有 Twisted ,它是所有協程框架中最古老的,甚至出現得比asyncio都要早。

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

很多人不知道的是,協程只是 Python 中編寫異步代碼的兩種方法之一。第二種方法是基於一個叫做 greenlet 的庫,你可以用 pip 安裝它。Greenlets 和協程類似,它們也允許一個 Python 函數暫停執行並稍後恢復,但是它們實現這點的方式完全不同,這意味著 Python 中的異步生態系統分成兩大類。

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

那麼在 greenlet 方面,跟asyncio對等的庫有哪些?我知道 3 個基於 greenlet 的異步包: Gevent 、 Eventlet 和 Meinheld ,儘管最後一個更像是一個 Web 伺服器而不是一個通用的異步庫。它們都有自己的異步循環實現,而且它們都提供了一個有趣的「monkey-patching」功能,取代了 Python 標準庫中的阻塞函數,例如那些執行網絡和線程的函數,並基於 greenlets 實現了等效的非阻塞版本。如果你有一些同步代碼想要異步運行,這些包會對你有所幫助。

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

但是,Flask 並不是唯一受益於 greenlets 的框架。其它 Web 框架,例如 Django 和 Bottle ,雖然沒有 greenlets,但也可以通過結合一個 greenlet Web 伺服器並使用 monkey-patching 修復阻塞函數的方式來異步運行。

異步比同步更快嗎?

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

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

上下文切換

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

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

高度優化的異步循環比作業系統在進行上下文切換方面更有效率,但根據我的經驗,要想看到實際的效率提升,你運行的並發量必須非常大。對於大部分應用程式,我不認為同步和異步上下文切換之間的性能差距有多明顯。

擴展性

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

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

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

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

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

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

  • 存在高負載(沒有高負載,訪問的高並發性就沒有優勢)
  • 任務是 I/O 綁定的(如果任務是 CPU 綁定的,那麼超過 CPU 數目的並發並沒有幫助)
  • 你查看單位時間內的平均請求處理數。如果你查看單個請求的處理時間,你不會看到有很大差別,甚至異步可能更慢,因為異步有更多並發的任務在爭奪 CPU。

結論

希望本文能解答異步代碼的一些困惑和誤解。我希望你能記住以下兩個關鍵點:

  • 異步應用程式只有在高負載下才會比同步應用程式做得更好
  • 多虧了 greenlets,即使你用一般方式寫代碼並使用 Flask 或 Django 之類的傳統框架,也能從異步中受益。

如果你想要了解更多關於異步系統如何工作的細節,可以查看 YouTube 上我在 PyCon 的演講 Asynchronous Python for the Complete Beginner 。

作者介紹:

Miguel Grinberg 是一名軟體工程師、攝影師和電影製作人,住在愛爾蘭的德羅赫拉。你可以在 Facebook 、 Google+ 、 LinkedIn 、 Github 和 Twitter 關注他。

原文連結:

https://blog.miguelgrinberg.com/post/sync-vs-async-python-what-is-the-difference

關注我並轉發此篇文章,私信我「領取資料」,即可免費獲得InfoQ價值4999元迷你書,點擊文末「了解更多」,即可移步InfoQ官網,獲取最新資訊~

相關焦點

  • 同步與異步Python有何不同?
    你是否聽到人們說過,異步 Python 代碼比「普通(或同步)Python 代碼更快?果真是那樣嗎?「同步」和「異步」是什麼意思?Web 應用程式通常要處理許多請求,這些請求在很短的時間段內來自不同的客戶端。為避免處理延遲,必須考慮並行處理多個請求,這通常稱為「並發」。
  • 摩託車同排量單缸發動機,同步雙缸,異步雙缸有何不同?
    這個話題2018年其實已經討論過了無數次,只不過今天又加入了一個新的概念,同步雙缸和異步雙缸,所以這個問題的討論就有些複雜了。但我們可以拆開再進行討論,先看看單缸發動機和雙缸發動機的主要差別。搞清楚了單缸發動機和雙缸發動機的優缺點,再來看看同步雙缸發動機和異步雙缸發動機的主要差異。同步雙缸發動機,簡單點來說就是把一個大缸體分成了兩個小缸體,並且是同步運行,看到這裡車友們肯定會說,既然是這樣和單缸發動機有何不同呢?
  • python多進程多線程-同步及異步調用
    python在多進程及多線程下,採用同步及異步調用方式的demo,同時展示耗時對比多進程源碼:from concurrent.futures import ProcessPoolExecutor # 進程池模塊import os, time, randomdef
  • 傻傻分不清 永磁同步電機與異步電機有何區別?
    映璇汽車工作室【買車疑難問閆哥】傻傻分不清 永磁同步電機與異步電機有何區別?閻哥:永磁電機,顧名思義就是:裡面帶磁芯的電機。它與異步電機最大的不同在於轉子獨特的結構,在轉子上放有高質量的永磁體磁極。而且,因為有磁芯,永磁電機成本較高。異步電機和永磁電機結構類似,只是沒有磁芯,(還記得中學課本上的切割磁力線產生電流嗎?)它的特點是:它在沒有通電的時候是沒有磁場的,所以,它很耗電,只有通過大量電才能產生磁場。它最高轉速能達到1.5萬轉/分鐘,是高速電機,高轉速產生大功率。但是,它成本較低,過載能力強,使用安裝維護方便。
  • python中的異步實踐與在tornado中的應用
    異步基礎要理解協程,先要理解異步,要理解異步,先要理解同步,與同步相關的概念又有阻塞與非阻塞,下面一一做簡單介紹。阻塞阻塞狀態指程序未得到所需計算資源時被掛起的狀態。程序在等待某個操作完成期間,自身無法繼續幹別的事情,則稱該程序在該操作上是阻塞的。
  • 純電動汽車搭載的交流異步電機與永磁同步電機有何區別?
    不管是說起特斯拉還是蔚來汽車的動力系統,我們都能聽到異步電機、永磁同步電機這兩個關鍵詞,那麼搭載了這兩種不同技術的電機有什麼優缺點呢?今天,就通過特斯拉Model S車型來一起聊一聊吧。了解完工作原理後我們就直接進入今天的主題,2019款特斯拉Model S長續航版前軸搭載的是永磁同步電機,後軸搭載的是交流異步電機,同步電機與異步電機的最大區別就在於兩者轉子速度是不是與定子旋轉的磁場速度一致,如果轉子的旋轉速度與定子是一樣的,那就叫同步電機,如果不一致,就叫異步電動機,具體到性能參數以及應用,兩者有很大的區別。永磁同步電機主要是由轉子、端蓋及定子組成。
  • Python基礎必備知識:同步異步阻塞非阻塞
    2.1 例子比如我去銀行辦理業務,可能會有兩種方式:第一種 :選擇排隊等候;第二種 :選擇取一個小紙條上面有我的號碼,等到排到我這一號時由櫃檯的人通知我輪到我去辦理業務了;第一種:前者(排隊等候)就是同步等待消息通知,也就是我要一直在等待銀行辦理業務情況;第二種:後者(等待別人通知)
  • 在python中使用aiomysql異步操作mysql
    ,所以在在網上查了下關於在python中異步的操作mysql,找來找去最後發現aiomysql的是實現最好的,現在簡單介紹一下它的使用。interactive_timeout的設置時間不同,所以這裡還要注意一下這個超時問題,在同步版本中關於mysql主動斷開連接的問題可以參考我之前的文章, ,異步版本同樣也要注意這個問題。
  • 網絡工程師的Python之路——netdev(異步並行)
    同步vs異步所謂同步,可以理解為每當系統執行完一段代碼或者函數後,系統將一直等待該段代碼或函數返回的值或消息,直到系統接收到返回的值或消息後才繼續往下執行下一段代碼或者函數,在等待返回值或消息的期間,程序處於阻塞狀態,系統將不做任何事情。
  • 同步,異步,阻塞,非阻塞
    同步與異步關注的是消息通知的機制,而阻塞與非阻塞關注的是程序(線程)等待消息通知時的狀態。以下載文件打個比方。同步阻塞:一直盯著下載進度條,到 100% 的時候就完成。同步體現在:等待下載完成通知;阻塞體現在:等待下載完成通知過程中,不能做其他任務處理;同步非阻塞:提交下載任務後就去幹別的,每過一段時間就去看一眼進度條,當是 100% 就完成。
  • 同步電機:與異步電機只有一處不同,你知道嗎?
    一、同步電機介紹同步電動機是屬於交流電機,定子繞組與異步電動機相同。它的轉子旋轉速度與定子繞組所產生的旋轉磁場的速度是一樣的,所以稱為同步電動機。其轉子轉速與定子旋轉磁場的轉速相同,轉子轉速n、磁極對數p和電源頻率f之間滿足公式:n=60f/p。
  • 聊聊同步、異步、阻塞與非阻塞
    也就是同步與異步主要是從消息通知機制角度來說的。有人也許會把阻塞調用和同步調用等同起來,實際上它們是不同的。所以同步的實現方式會有兩種:同步阻塞、同步非阻塞;同理,異步也會有兩種實現:異步阻塞、異步非阻塞;對於阻塞調用來說,則當前線程就會被掛起等待當前函數返回
  • 異步電動機和同步電動機有什麼區別?
    電動機在各行各業都會用得到,在各種類型的電動機中,異步電動機是最為常見的一種動力設備,在有的場合也會見到同步電機的使用。同樣是交流電機,到底異步電機和同步電機有什麼區別呢?今天借著這個問題與朋友們探討一下這兩種電機的區別。
  • 為什麼比亞迪偏愛永磁同步電機,特斯拉寶馬喜歡交流異步電機?
    在電機系統方面,用戶一定對「永磁同步電機」不陌生,的確,這是目前大多產品會搭載的電機,但關注特斯拉的朋友們可能知道, 「電動大鱷」特斯拉搭載的是「交流異步電機」,那麼同步和異步究竟有何區別?此外,永磁同步電機還擁有噪音較小的特點。至於缺點,永磁體依賴稀土,如果想要大的功率,就要大塊的永磁體,造價高;而且雖然叫「永磁」,但它在高溫之類的惡劣環境下容易退磁。至於異步電機,成本十分低廉,工藝簡單、運行可靠、維修方便等特點,能夠在複雜的工作環境中工作,也對周圍工作溫度的大幅度變化有比較強的適應能力。
  • 同步串行通信和異步串行通信
    不同的串行通信方式具有不同的數據格式。下面簡單介紹一下常用的兩種基本串行通信方式:同步通信和異步通信及其數據傳送格式。  同步通信  所謂同步通信是指在約定的通信速率下,發送端和接收端的時鐘信號頻率和相信始終保持一致(同步),這就保證了通信雙方在發送和接收數據時具有完全一致的定時關係。  同步通信把許多字符組成一個信息組,或稱為信息幀,每幀的開始用同步字符來指示。
  • SSD講解 同步快閃記憶體/異步快閃記憶體區別分析
    而同一個品牌,採用了相同的主控,不同的晶片之後,性能差距就會有明顯的區別,比如OCZ之前採用SandForce SF-2281時,但是通過不同的快閃記憶體與固件搭配劃分出    可見SSD所用的固件與快閃記憶體種類都是對其性能有相當大影響的,那麼對於同步快閃記憶體與異步快閃記憶體的區分。
  • 面試官:給我說說什麼是同步異步?
    話不多說,開始今天的學習:一、同步、異步請求瀏覽器發送請求給伺服器,其有同步請求和異步請求兩種方式。1同步請求什麼叫同步請求呢?如果其中有一個信息填寫錯了,請求失敗,又要全部重新填寫,會很麻煩繁瑣。我只填寫我填錯了的不就好了麼?如何解決這個問題?就需要引入異步的概念了。
  • 面試官:給我說說什麼是同步異步?
    話不多說,開始今天的學習:一、同步、異步請求瀏覽器發送請求給伺服器,其有同步請求和異步請求兩種方式。1同步請求什麼叫同步請求呢?就是在發送一個請求之後,需要等待伺服器響應返回,才能夠發送下一個請求。如果其中有一個信息填寫錯了,請求失敗,又要全部重新填寫,會很麻煩繁瑣。我只填寫我填錯了的不就好了麼?如何解決這個問題?就需要引入異步的概念了。2異步請求和同步請求相對,異步不需要等待響應,隨時可以發送下一次請求。
  • 同步和異步的區別電平異步時序邏輯電路
    脈衝異步時序電路和同步時序電路有兩個共同的特點:● 電路狀態的轉換是在脈衝作用下實現的。在同步時序電路中儘管輸入信號可以是電平信號或者脈衝信號,但電路的狀態轉換受統一的時鐘脈衝控制;脈衝異步時序電路中沒有統一的時鐘脈衝,因此,規定輸入信號為脈衝信號,即控制電路狀態轉換的脈衝由電路狀態輸入端直接提供。● 電路對過去輸入信號的記憶是由觸發器實現的。
  • 永磁同步與異步感應:為何高端電動車,必須同時要裝這兩種電機?
    現階段在售新能源車所採用的電機種類無外乎兩種:永磁同步電機和感應異步電機。這兩種技術有何區別?為何特斯拉Model 3、蔚來這樣的高端電動車,都是兩種電機一前一後排列呢?「電機和電機之間有啥不同?」我們初中就學過,所謂電機,指的是依據電磁感應定律實現電能轉換或傳遞的一種電磁裝置。