溫馨提示×

溫馨提示×

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

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

Python中Sync與Async執(zhí)行速度快慢實(shí)例對比分析

發(fā)布時(shí)間:2023-03-01 15:33:24 來源:億速云 閱讀:109 作者:iii 欄目:開發(fā)技術(shù)

這篇文章主要講解了“Python中Sync與Async執(zhí)行速度快慢實(shí)例對比分析”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“Python中Sync與Async執(zhí)行速度快慢實(shí)例對比分析”吧!

1.一個(gè)簡單的例子

首先先從一個(gè)例子了解兩種調(diào)用方法的差別, 為了能清晰的看出他們的運(yùn)行時(shí)長差別, 都讓他們重復(fù)運(yùn)行10000次, 具體代碼如下:

import asyncio
import time


n_call = 10000


# sync的調(diào)用時(shí)長
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的調(diào)用時(shí)長
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的語法比較不一樣, 函數(shù)需要使用async def開頭, 同時(shí)調(diào)用async def函數(shù)需要使用await語法, 運(yùn)行的時(shí)候需要先獲取線程的事件循環(huán), 然后在通過事件循環(huán)來運(yùn)行async_main函數(shù)來達(dá)到一樣的效果, 但是從運(yùn)行結(jié)果的輸出可以看得出, sync的語法在這個(gè)場景中比async的語法速度快了一些些(由于Python的GIL原因, 這里無法使用多核的性能, 只能以單核來跑)。

造成這樣的原因是同樣由同一個(gè)線程執(zhí)行的情況下(cpu單核心),async的調(diào)用還需要經(jīng)過一些事件循環(huán)的額外調(diào)用, 這會產(chǎn)生一些小開銷, 從而運(yùn)行時(shí)間會比sync的慢, 同時(shí)這是一個(gè)純cpu運(yùn)算的示例, 而async的的優(yōu)勢在于網(wǎng)絡(luò)io運(yùn)算, 在這個(gè)場景無法發(fā)揮優(yōu)勢, 但會在高并發(fā)場景則會大放光彩, 造成這樣的原因則是因?yàn)?code>async是以協(xié)程運(yùn)行的, sync是以線程運(yùn)行的。

NOTE: 目前所說的async語法都是支持網(wǎng)絡(luò)io, 而文件系統(tǒng)的異步io還不是非常的完善, 所以文件系統(tǒng)的異步讀寫是通過封裝交給多線程去處理, 而不是協(xié)程。 

2.一個(gè)io的例子

為了了解async在io場景下的運(yùn)行優(yōu)勢, 先假定有一個(gè)io場景--Web后臺服務(wù)通常需要處理許多請求, 所有請求都是從不同的客戶端發(fā)出的, 示例如圖:

Python中Sync與Async執(zhí)行速度快慢實(shí)例對比分析

在這種場景下, 客戶端請求都是在短時(shí)間內(nèi)發(fā)出的。 而服務(wù)端為了能夠在短時(shí)間內(nèi)處理大量的請求, 防止處理延遲, 都會以某種方式來支持并發(fā)或者并行。

NOTE: 并發(fā),在操作系統(tǒng)中,是指一個(gè)時(shí)間段中有幾個(gè)程序都處于已啟動運(yùn)行到運(yùn)行完畢之間,且這幾個(gè)程序都是在同一個(gè)處理機(jī)上運(yùn)行,但任一個(gè)時(shí)刻點(diǎn)上只有一個(gè)程序在處理機(jī)上運(yùn)行。 并行是計(jì)算機(jī)系統(tǒng)中能同時(shí)執(zhí)行兩個(gè)或多個(gè)處理的一種計(jì)算方法。

對于sync語法來說, 這個(gè)Web后臺可以通過進(jìn)程, 線程或者兩者結(jié)合來實(shí)現(xiàn), 他們的提供并發(fā)/并行的能力會局限于woker的數(shù)量, 比如當(dāng)有5個(gè)客戶端同時(shí)請求而服務(wù)端只有4個(gè)worker時(shí), 有一個(gè)請求會進(jìn)入阻塞等待階段, 直到運(yùn)行的4個(gè)worker有一個(gè)被處理完畢。 為了讓服務(wù)器能提供更好的服務(wù), 我們都會提供足夠多的worker, 同時(shí)由于進(jìn)程具有良好的隔離性且比較每起一個(gè)進(jìn)程都會占用一份獨(dú)立的資源, 所以都是以幾個(gè)進(jìn)程+大量線程的形式來提供服務(wù)。

NOTE: 進(jìn)程是最小的資源分配單位, 過多的進(jìn)程會占用很多系統(tǒng)資源, 一般的后臺服務(wù)啟用的進(jìn)程數(shù)量不會很多, 同時(shí)線程是最小的調(diào)度單位, 所以以下的調(diào)度我都以線程來描述。

但是這種方式是很耗系統(tǒng)的資源的(相對于協(xié)程來說), 因?yàn)榫€程的運(yùn)行都是靠cpu來執(zhí)行的, 而cpu是有限的, 同一時(shí)刻只能支持固定的幾個(gè)worker運(yùn)行, 其他線程則得等待被調(diào)度, 這樣就意味著每個(gè)線程都只能工作一個(gè)時(shí)間分片, 之后就會被調(diào)度系統(tǒng)控制進(jìn)入阻塞或者就緒階段, 讓位給其他線程, 直到下次獲取時(shí)間分片時(shí)才可以繼續(xù)運(yùn)行。 為了能模擬出同一時(shí)刻內(nèi), 多個(gè)線程同時(shí)運(yùn)行, 且防止其他線程餓死的情況, 線程每次獲得的運(yùn)行時(shí)間很短, 線程間的調(diào)度切換很頻繁, 當(dāng)啟用更多的進(jìn)程和更多的線程時(shí), 調(diào)度就會更加的頻繁。

不過調(diào)度線程的開銷還不算大, 比較大的開銷是調(diào)度線程而產(chǎn)生的下文切換和競爭條件(具體可以參考《計(jì)算機(jī)導(dǎo)論》中進(jìn)程調(diào)度相關(guān)的資料, 我這里只是簡單說明), cpu在執(zhí)行代碼時(shí),它需要把數(shù)據(jù)加載到cpu的緩存中去的再運(yùn)行, 當(dāng)cpu運(yùn)行的線程在這個(gè)時(shí)間分片內(nèi)執(zhí)行完成時(shí), 該線程的最新運(yùn)行數(shù)據(jù)就會保存起來, 然后cpu會去加載準(zhǔn)備被調(diào)度的線程的數(shù)據(jù), 并運(yùn)行。 雖然這部分暫存數(shù)據(jù)是保存在比內(nèi)存更快, 比內(nèi)存更靠近c(diǎn)pu的寄存器上, 但是寄存器的訪問速度也沒有cpu緩存的訪問速度快, 所以cpu在切換運(yùn)行的線程時(shí), 都會花上一部分時(shí)間用來裝載數(shù)據(jù)上還有裝載緩存時(shí)的競爭問題。

對比線程的調(diào)度產(chǎn)生的上下文切換與搶占式, async語法實(shí)現(xiàn)的協(xié)程是非搶占式的, 協(xié)程的調(diào)度是依賴于一個(gè)循環(huán)來控制, 這個(gè)循環(huán)是一個(gè)非常常高效的任務(wù)管理器和調(diào)度器, 由于調(diào)度的是一段代碼的實(shí)現(xiàn)邏輯, 所以cpu的執(zhí)行代碼并不用切換, 也就沒有上下文切換的開銷, 同時(shí), 也不用考慮裝載緩存的競爭問題。 還是以上面那個(gè)圖為例子, 在服務(wù)開始啟動時(shí), 會先啟動一個(gè)事件循環(huán), 當(dāng)收到請求時(shí), 它會創(chuàng)建一個(gè)任務(wù)來處理客戶端發(fā)送過來的請求, 這個(gè)任務(wù)會從事件循環(huán)獲取到了執(zhí)行權(quán),獨(dú)占整個(gè)線程資源并一直執(zhí)行, 直到遇到需要等待外部事件, 比如等待數(shù)據(jù)庫返回?cái)?shù)據(jù)的事件, 這時(shí)任務(wù)會告訴事件循環(huán)自己在等待這個(gè)事件, 然后交出執(zhí)行權(quán), 事件循環(huán)就會把執(zhí)行權(quán)傳遞給最需要運(yùn)行的任務(wù)。 當(dāng)剛才交出執(zhí)行權(quán)的任務(wù)在后續(xù)收到數(shù)據(jù)庫事件響應(yīng)時(shí), 事件循環(huán)會把它安排到就緒列表的第一個(gè)(不同的事件循環(huán)實(shí)現(xiàn)可能不一樣)并在下一次切換執(zhí)行權(quán)時(shí), 把執(zhí)行權(quán)返回給他, 讓他繼續(xù)執(zhí)行, 直到遇到下一個(gè)等待事件。

這種切換協(xié)程的方式稱為協(xié)作式多任務(wù)處理, 由于只會在單個(gè)進(jìn)程或者單個(gè)線程中運(yùn)行, 切換協(xié)程時(shí)上下文是不用改變的, cpu不用重新讀寫緩存, 所以會節(jié)省一些開銷。 從上面可以看出協(xié)作式切換執(zhí)行權(quán)是基于協(xié)程自己主動讓出的, 而線程是搶占式的, 線程在沒遇到io事件時(shí), 也可能從運(yùn)行狀態(tài)轉(zhuǎn)為就緒狀態(tài), 直到再次被調(diào)用, 這樣會多出很多調(diào)度帶來的開銷, 而協(xié)程是會一直運(yùn)行, 直到遇到讓步事件才切換, 所以協(xié)程調(diào)度的次數(shù)會比線程少很多。 同時(shí)可以看出協(xié)程的何時(shí)調(diào)度是由開發(fā)者指定(比如上面所說的等等數(shù)據(jù)庫返回事件), 而且是非搶占式的, 這就意味著某個(gè)協(xié)程在運(yùn)行時(shí), 其他協(xié)程是沒辦法運(yùn)行的, 只能等到運(yùn)行的協(xié)程交出執(zhí)行權(quán), 所以開發(fā)者要確保不能讓任務(wù)在cpu上停留太長時(shí)間,否則剩余的任務(wù)就會餓死。

感謝各位的閱讀,以上就是“Python中Sync與Async執(zhí)行速度快慢實(shí)例對比分析”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對Python中Sync與Async執(zhí)行速度快慢實(shí)例對比分析這一問題有了更深刻的體會,具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識點(diǎn)的文章,歡迎關(guān)注!

向AI問一下細(xì)節(jié)

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

AI