這篇文章主要講解了“Python中Sync與Async執行速度快慢實例對比分析”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Python中Sync與Async執行速度快慢實例對比分析”吧!
首先先從一個例子了解兩種調用方法的差別, 為了能清晰的看出他們的運行時長差別, 都讓他們重復運行10000次, 具體代碼如下:
import asyncio import time n_call = 10000 # sync的調用時長 def demo(n: int) -> int: return n ** n s_time = time.time() for i in range(n_call): demo(i) print(time.time() - s_time) # async的調用時長 async def sub_demo(n: int) -> int: return n ** n async def async_main() -> None: for i in range(n_call): await sub_demo(i) loop = asyncio.get_event_loop() s_time = time.time() loop.run_until_complete(async_main()) print(time.time() - s_time) # 輸出 # 5.310615682601929 # 5.614157438278198
可以看得出來, sync
的語法大家都是很熟悉, 而async
的語法比較不一樣, 函數需要使用async def
開頭, 同時調用async def
函數需要使用await
語法, 運行的時候需要先獲取線程的事件循環, 然后在通過事件循環來運行async_main
函數來達到一樣的效果, 但是從運行結果的輸出可以看得出, sync
的語法在這個場景中比async
的語法速度快了一些些(由于Python的GIL原因, 這里無法使用多核的性能, 只能以單核來跑)。
造成這樣的原因是同樣由同一個線程執行的情況下(cpu單核心),async
的調用還需要經過一些事件循環的額外調用, 這會產生一些小開銷, 從而運行時間會比sync
的慢, 同時這是一個純cpu運算的示例, 而async
的的優勢在于網絡io運算, 在這個場景無法發揮優勢, 但會在高并發場景則會大放光彩, 造成這樣的原因則是因為async
是以協程運行的, sync
是以線程運行的。
NOTE: 目前所說的async
語法都是支持網絡io, 而文件系統的異步io還不是非常的完善, 所以文件系統的異步讀寫是通過封裝交給多線程去處理, 而不是協程。
為了了解async
在io場景下的運行優勢, 先假定有一個io場景--Web后臺服務通常需要處理許多請求, 所有請求都是從不同的客戶端發出的, 示例如圖:
在這種場景下, 客戶端請求都是在短時間內發出的。 而服務端為了能夠在短時間內處理大量的請求, 防止處理延遲, 都會以某種方式來支持并發或者并行。
NOTE: 并發,在操作系統中,是指一個時間段中有幾個程序都處于已啟動運行到運行完畢之間,且這幾個程序都是在同一個處理機上運行,但任一個時刻點上只有一個程序在處理機上運行。 并行是計算機系統中能同時執行兩個或多個處理的一種計算方法。
對于sync
語法來說, 這個Web后臺可以通過進程, 線程或者兩者結合來實現, 他們的提供并發/并行的能力會局限于woker的數量, 比如當有5個客戶端同時請求而服務端只有4個worker時, 有一個請求會進入阻塞等待階段, 直到運行的4個worker有一個被處理完畢。 為了讓服務器能提供更好的服務, 我們都會提供足夠多的worker, 同時由于進程具有良好的隔離性且比較每起一個進程都會占用一份獨立的資源, 所以都是以幾個進程+大量線程的形式來提供服務。
NOTE: 進程是最小的資源分配單位, 過多的進程會占用很多系統資源, 一般的后臺服務啟用的進程數量不會很多, 同時線程是最小的調度單位, 所以以下的調度我都以線程來描述。
但是這種方式是很耗系統的資源的(相對于協程來說), 因為線程的運行都是靠cpu來執行的, 而cpu是有限的, 同一時刻只能支持固定的幾個worker運行, 其他線程則得等待被調度, 這樣就意味著每個線程都只能工作一個時間分片, 之后就會被調度系統控制進入阻塞或者就緒階段, 讓位給其他線程, 直到下次獲取時間分片時才可以繼續運行。 為了能模擬出同一時刻內, 多個線程同時運行, 且防止其他線程餓死的情況, 線程每次獲得的運行時間很短, 線程間的調度切換很頻繁, 當啟用更多的進程和更多的線程時, 調度就會更加的頻繁。
不過調度線程的開銷還不算大, 比較大的開銷是調度線程而產生的下文切換和競爭條件(具體可以參考《計算機導論》中進程調度相關的資料, 我這里只是簡單說明), cpu在執行代碼時,它需要把數據加載到cpu的緩存中去的再運行, 當cpu運行的線程在這個時間分片內執行完成時, 該線程的最新運行數據就會保存起來, 然后cpu會去加載準備被調度的線程的數據, 并運行。 雖然這部分暫存數據是保存在比內存更快, 比內存更靠近cpu的寄存器上, 但是寄存器的訪問速度也沒有cpu緩存的訪問速度快, 所以cpu在切換運行的線程時, 都會花上一部分時間用來裝載數據上還有裝載緩存時的競爭問題。
對比線程的調度產生的上下文切換與搶占式, async
語法實現的協程是非搶占式的, 協程的調度是依賴于一個循環來控制, 這個循環是一個非常常高效的任務管理器和調度器, 由于調度的是一段代碼的實現邏輯, 所以cpu的執行代碼并不用切換, 也就沒有上下文切換的開銷, 同時, 也不用考慮裝載緩存的競爭問題。 還是以上面那個圖為例子, 在服務開始啟動時, 會先啟動一個事件循環, 當收到請求時, 它會創建一個任務來處理客戶端發送過來的請求, 這個任務會從事件循環獲取到了執行權,獨占整個線程資源并一直執行, 直到遇到需要等待外部事件, 比如等待數據庫返回數據的事件, 這時任務會告訴事件循環自己在等待這個事件, 然后交出執行權, 事件循環就會把執行權傳遞給最需要運行的任務。 當剛才交出執行權的任務在后續收到數據庫事件響應時, 事件循環會把它安排到就緒列表的第一個(不同的事件循環實現可能不一樣)并在下一次切換執行權時, 把執行權返回給他, 讓他繼續執行, 直到遇到下一個等待事件。
這種切換協程的方式稱為協作式多任務處理, 由于只會在單個進程或者單個線程中運行, 切換協程時上下文是不用改變的, cpu不用重新讀寫緩存, 所以會節省一些開銷。 從上面可以看出協作式切換執行權是基于協程自己主動讓出的, 而線程是搶占式的, 線程在沒遇到io事件時, 也可能從運行狀態轉為就緒狀態, 直到再次被調用, 這樣會多出很多調度帶來的開銷, 而協程是會一直運行, 直到遇到讓步事件才切換, 所以協程調度的次數會比線程少很多。 同時可以看出協程的何時調度是由開發者指定(比如上面所說的等等數據庫返回事件), 而且是非搶占式的, 這就意味著某個協程在運行時, 其他協程是沒辦法運行的, 只能等到運行的協程交出執行權, 所以開發者要確保不能讓任務在cpu上停留太長時間,否則剩余的任務就會餓死。
感謝各位的閱讀,以上就是“Python中Sync與Async執行速度快慢實例對比分析”的內容了,經過本文的學習后,相信大家對Python中Sync與Async執行速度快慢實例對比分析這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。