最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Python異步編程asyncio的核心用法與避坑指南

 更新時(shí)間:2026年03月26日 08:22:10   作者:傻啦嘿喲  
本文介紹了Python異步編程的基本概念、核心價(jià)值及其實(shí)現(xiàn)機(jī)制,并詳細(xì)解釋了如何通過async和await關(guān)鍵字來編寫異步代碼,同時(shí)列舉了初學(xué)者常犯的錯(cuò)誤及避免這些問題的方法,需要的朋友可以參考下

你的爬蟲程序又卡住了。

不是代碼寫錯(cuò)了,是請(qǐng)求發(fā)得太慢。一萬條數(shù)據(jù)要爬,一條一秒,兩個(gè)多小時(shí)才能跑完。你試著用多線程,結(jié)果IP被封得更快了。線程開太多,系統(tǒng)資源也扛不住。你聽說過異步,聽說它能用單線程搞定高并發(fā),但看著asyncawait這兩個(gè)關(guān)鍵字,總覺得像在寫另一門語(yǔ)言。

這種感覺不怪你。異步編程確實(shí)和Python其他部分不太一樣,但一旦跨過那道坎,你會(huì)發(fā)現(xiàn)它沒有想象中那么難。

異步到底在解決什么問題

先看一個(gè)很簡(jiǎn)單的程序:

import?time

def?task(name,?seconds):
????print(f"{name}?開始")
????time.sleep(seconds)
????print(f"{name}?結(jié)束")

start?=?time.time()
task("任務(wù)1",?2)
task("任務(wù)2",?2)
print(f"總耗時(shí):?{time.time()?-?start}")

運(yùn)行結(jié)果是:任務(wù)1開始、等待兩秒、任務(wù)1結(jié)束、任務(wù)2開始、再等兩秒、任務(wù)2結(jié)束,總耗時(shí)4秒。

問題出在time.sleep這里。當(dāng)程序執(zhí)行到sleep時(shí),CPU其實(shí)什么事都沒干,就傻等著時(shí)間過去。這段時(shí)間本可以用來處理其他任務(wù),但同步代碼不允許這樣做——它是一條路走到黑的,沒執(zhí)行完當(dāng)前函數(shù),絕不會(huì)跳到下一行。

異步要解決的就是這個(gè)“等待浪費(fèi)”的問題。當(dāng)某個(gè)操作需要等待時(shí),讓程序暫時(shí)離開,去做別的事,等那個(gè)操作準(zhǔn)備好了再回來繼續(xù)。

async和await到底在干什么

Python的異步編程基于一個(gè)叫做“事件循環(huán)”的東西。你可以把它理解成一個(gè)調(diào)度中心,管理著所有待執(zhí)行的任務(wù)。當(dāng)一個(gè)任務(wù)遇到需要等待的操作時(shí),它會(huì)告訴事件循環(huán):“我先去忙別的,好了叫我”,然后事件循環(huán)就會(huì)切換到下一個(gè)可以執(zhí)行的任務(wù)。

async def用來定義一個(gè)異步函數(shù),也叫協(xié)程。它和普通函數(shù)的區(qū)別在于,調(diào)用它并不會(huì)立即執(zhí)行,而是返回一個(gè)協(xié)程對(duì)象。

async?def?hello():
????print("Hello")
????await?asyncio.sleep(1)
????print("World")

await用來等待一個(gè)異步操作完成。當(dāng)程序執(zhí)行到await時(shí),它會(huì)暫時(shí)離開這個(gè)函數(shù),讓事件循環(huán)去處理其他任務(wù)。等asyncio.sleep(1)這個(gè)操作完成了,事件循環(huán)才會(huì)回來繼續(xù)執(zhí)行后面的print("World")

來看一個(gè)完整的例子:

import?asyncio

async?def?task(name,?seconds):
????print(f"{name}?開始")
????await?asyncio.sleep(seconds)
????print(f"{name}?結(jié)束")

async?def?main():
????#?創(chuàng)建三個(gè)任務(wù),但還沒有執(zhí)行
????tasks?=?[
????????asyncio.create_task(task("任務(wù)1",?2)),
????????asyncio.create_task(task("任務(wù)2",?1)),
????????asyncio.create_task(task("任務(wù)3",?3))
????]
????#?等待所有任務(wù)完成
????await?asyncio.gather(*tasks)

asyncio.run(main())

運(yùn)行結(jié)果:

任務(wù)1?開始
任務(wù)2?開始
任務(wù)3?開始
任務(wù)2?結(jié)束
任務(wù)1?結(jié)束
任務(wù)3?結(jié)束
總耗時(shí)約3秒

三個(gè)任務(wù)幾乎是同時(shí)開始的,總共只用了最慢那個(gè)任務(wù)的時(shí)間。這就是異步的核心價(jià)值:在等待的時(shí)間里做別的事。

事件循環(huán)是怎么運(yùn)轉(zhuǎn)的

理解事件循環(huán)的工作機(jī)制,能幫你避開大部分異步編程的坑。

事件循環(huán)本質(zhì)上是一個(gè)無限循環(huán),它維護(hù)著兩個(gè)隊(duì)列:就緒隊(duì)列和等待隊(duì)列。就緒隊(duì)列里放著可以立即執(zhí)行的任務(wù),等待隊(duì)列里放著正在等待某個(gè)事件(比如網(wǎng)絡(luò)響應(yīng)、定時(shí)器到期)的任務(wù)。

每一次循環(huán),事件循環(huán)會(huì)從就緒隊(duì)列里取出一個(gè)任務(wù)執(zhí)行。當(dāng)任務(wù)執(zhí)行到await時(shí),它會(huì)把自己掛起,并告訴事件循環(huán)它正在等待什么。事件循環(huán)就把這個(gè)任務(wù)放到等待隊(duì)列里,然后繼續(xù)處理就緒隊(duì)列中的下一個(gè)任務(wù)。

當(dāng)?shù)却?duì)列里的某個(gè)任務(wù)等的事件發(fā)生了(比如定時(shí)器到期),事件循環(huán)就會(huì)把它移回就緒隊(duì)列,等待下一次被調(diào)度執(zhí)行。

這就是為什么異步程序能在一個(gè)線程里實(shí)現(xiàn)并發(fā):它不是同時(shí)做多件事,而是在一件事等待的時(shí)候,去做另一件事。

新手最容易踩的坑

坑一:在異步函數(shù)里用了同步阻塞操作

async?def?bad_example():
????#?錯(cuò)誤:用了?time.sleep?而不是?asyncio.sleep
????time.sleep(5)??#?這會(huì)阻塞整個(gè)事件循環(huán)
????return?"done"

time.sleep是同步阻塞的,它會(huì)卡住當(dāng)前線程,事件循環(huán)在這5秒內(nèi)完全無法工作,所有任務(wù)都會(huì)被堵住。正確的做法是用await asyncio.sleep(5)

同樣的道理,如果你在異步代碼里使用了requests庫(kù)發(fā)HTTP請(qǐng)求,它也會(huì)阻塞事件循環(huán)。應(yīng)該用aiohttp這類異步HTTP客戶端。

坑二:忘記加await

async?def?wrong():
????coro?=?some_async_function()??#?這返回的是協(xié)程對(duì)象,不是執(zhí)行結(jié)果
????print(coro)??#?打印?<coroutine?object...>
????#?協(xié)程對(duì)象沒有被?await,它永遠(yuǎn)不會(huì)執(zhí)行

協(xié)程對(duì)象被創(chuàng)建后,必須被await或者被asyncio.create_task()調(diào)度,否則它不會(huì)執(zhí)行。這是異步編程新手最容易忽略的地方。

坑三:在同步代碼里調(diào)用異步函數(shù)

def?sync_function():
????#?錯(cuò)誤:不能在同步函數(shù)里直接?await
????result?=?await?async_function()??#?SyntaxError

await只能在async def定義的函數(shù)內(nèi)部使用。如果你想在同步代碼里執(zhí)行異步函數(shù),需要用asyncio.run()或者asyncio.create_task()配合事件循環(huán)。

坑四:創(chuàng)建了太多任務(wù)導(dǎo)致資源耗盡

#?錯(cuò)誤:一下子創(chuàng)建幾萬個(gè)任務(wù)
for?i?in?range(100000):
????asyncio.create_task(heavy_task(i))
await?asyncio.gather(*tasks)??#?可能內(nèi)存爆炸

雖然協(xié)程比線程輕量很多,但也不是無限創(chuàng)建的。幾萬個(gè)協(xié)程同時(shí)存在,內(nèi)存和調(diào)度開銷還是會(huì)很大??梢杂眯盘?hào)量(Semaphore)來控制并發(fā)數(shù)量。

控制并發(fā)數(shù)的正確姿勢(shì)

在實(shí)際項(xiàng)目中,你通常不會(huì)把所有任務(wù)一次性丟進(jìn)事件循環(huán)。以爬蟲為例,同時(shí)發(fā)幾百個(gè)請(qǐng)求,目標(biāo)網(wǎng)站可能直接把你IP封了,你自己的網(wǎng)絡(luò)也可能扛不住。

asyncio.Semaphore可以輕松控制并發(fā)數(shù)量:

import?asyncio
import?aiohttp

async?def?fetch(session,?url,?semaphore):
????async?with?semaphore:??#?獲取信號(hào)量,控制并發(fā)數(shù)
????????async?with?session.get(url)?as?response:
????????????return?await?response.text()

async?def?main():
????urls?=?["http://example.com"]?*?100??#?100個(gè)URL
????semaphore?=?asyncio.Semaphore(10)??#?限制最多同時(shí)10個(gè)請(qǐng)求
????
????async?with?aiohttp.ClientSession()?as?session:
????????tasks?=?[fetch(session,?url,?semaphore)?for?url?in?urls]
????????results?=?await?asyncio.gather(*tasks)
????
????print(f"抓取完成,共{len(results)}個(gè)頁(yè)面")

asyncio.run(main())

信號(hào)量的工作原理很簡(jiǎn)單:它內(nèi)部維護(hù)一個(gè)計(jì)數(shù)器。每次async with semaphore會(huì)嘗試減少計(jì)數(shù)器,如果計(jì)數(shù)器大于0,就允許進(jìn)入;如果等于0,就等待直到有任務(wù)釋放信號(hào)量。這樣就能精確控制并發(fā)數(shù),既充分利用資源,又不至于把對(duì)方服務(wù)器打趴下。

超時(shí)處理是保命技能

異步程序里,某個(gè)任務(wù)卡住會(huì)影響整個(gè)事件循環(huán)嗎?不會(huì),因?yàn)?code>await是讓出控制權(quán)的。但如果一個(gè)任務(wù)內(nèi)部有死循環(huán)或者一直沒遇到await,它確實(shí)會(huì)霸占事件循環(huán),導(dǎo)致其他任務(wù)無法執(zhí)行。

更常見的問題是網(wǎng)絡(luò)請(qǐng)求一直不返回。這時(shí)候就需要超時(shí)控制:

import?asyncio

async?def?fetch_with_timeout():
????try:
????????#?設(shè)置5秒超時(shí)
????????result?=?await?asyncio.wait_for(slow_operation(),?timeout=5)
????????return?result
????except?asyncio.TimeoutError:
????????print("操作超時(shí)")
????????return?None

async?def?slow_operation():
????await?asyncio.sleep(10)??#?模擬慢操作
????return?"數(shù)據(jù)"

asyncio.wait_for會(huì)給一個(gè)協(xié)程加上時(shí)間限制。超時(shí)后它會(huì)拋出asyncio.TimeoutError,你可以捕獲它做降級(jí)處理。

還有一個(gè)更靈活的工具是asyncio.gatherreturn_exceptions參數(shù):

results?=?await?asyncio.gather(
????task1(),
????task2(),
????task3(),
????return_exceptions=True??#?異常不會(huì)中斷,而是作為結(jié)果返回
)

for?result?in?results:
????if?isinstance(result,?Exception):
????????print(f"任務(wù)失敗:?{result}")
????else:
????????print(f"任務(wù)成功:?{result}")

這樣單個(gè)任務(wù)失敗不會(huì)導(dǎo)致整個(gè)gather崩潰,你可以優(yōu)雅地處理每個(gè)任務(wù)的結(jié)果。

異步和并發(fā)的本質(zhì)區(qū)別

很多人會(huì)把異步和多線程混淆,覺得它們都能“同時(shí)”做多件事。但理解它們的區(qū)別,能幫你做出更合理的技術(shù)選型。

多線程是操作系統(tǒng)層面的并發(fā)。每個(gè)線程都有自己的??臻g,線程切換由操作系統(tǒng)調(diào)度,開銷較大。Python因?yàn)橛蠫IL(全局解釋器鎖),多線程在CPU密集型任務(wù)上反而更慢,但在IO密集型任務(wù)上依然有用。

異步是單線程內(nèi)的并發(fā)。所有協(xié)程共享同一個(gè)線程,切換發(fā)生在await的時(shí)候,開銷極小。一個(gè)事件循環(huán)可以輕松處理上萬協(xié)程,而開上萬個(gè)線程基本不可能。

選哪個(gè)?IO密集型任務(wù)(網(wǎng)絡(luò)請(qǐng)求、文件讀寫、數(shù)據(jù)庫(kù)查詢)用異步,代碼更簡(jiǎn)潔,資源消耗更少。CPU密集型任務(wù)(計(jì)算、加密、壓縮)用多進(jìn)程,或者把計(jì)算部分交給專門的進(jìn)程池,異步只負(fù)責(zé)調(diào)度。

實(shí)際項(xiàng)目中的異步架構(gòu)

真實(shí)項(xiàng)目很少只用一個(gè)asyncio.run()就完事。更常見的做法是分層設(shè)計(jì):

底層是異步IO操作,用aiohttp、aiomysqlaioredis這些異步庫(kù)。中間層是業(yè)務(wù)邏輯,用async def定義協(xié)程,處理數(shù)據(jù)轉(zhuǎn)換、錯(cuò)誤重試、超時(shí)控制。最上層是調(diào)度層,管理任務(wù)隊(duì)列、控制并發(fā)、監(jiān)控執(zhí)行狀態(tài)。

一個(gè)典型的數(shù)據(jù)采集任務(wù)大概是這樣的:

import?asyncio
import?aiohttp
from?asyncio?import?Queue

async?def?worker(name,?queue,?session):
????"""工作協(xié)程:從隊(duì)列取URL,抓取頁(yè)面"""
????while?True:
????????url?=?await?queue.get()
????????try:
????????????async?with?session.get(url,?timeout=aiohttp.ClientTimeout(total=10))?as?resp:
????????????????content?=?await?resp.text()
????????????????print(f"{name}?抓取?{url}?成功,長(zhǎng)度?{len(content)}")
????????????????#?這里可以存儲(chǔ)數(shù)據(jù)
????????except?Exception?as?e:
????????????print(f"{name}?抓取?{url}?失敗:?{e}")
????????finally:
????????????queue.task_done()

async?def?main():
????urls?=?[f"http://example.com/page/{i}"?for?i?in?range(1000)]
????queue?=?Queue()
????
????#?把所有URL放入隊(duì)列
????for?url?in?urls:
????????await?queue.put(url)
????
????async?with?aiohttp.ClientSession()?as?session:
????????#?啟動(dòng)5個(gè)工作協(xié)程
????????workers?=?[asyncio.create_task(worker(f"worker-{i}",?queue,?session))?
???????????????????for?i?in?range(5)]
????????
????????#?等待所有任務(wù)完成
????????await?queue.join()
????????
????????#?取消工作協(xié)程
????????for?w?in?workers:
????????????w.cancel()

asyncio.run(main())

隊(duì)列模式的好處是解耦了任務(wù)生產(chǎn)者和消費(fèi)者,可以靈活調(diào)整并發(fā)數(shù)量,也方便做斷點(diǎn)續(xù)傳、失敗重試這些復(fù)雜邏輯。

寫在最后

異步編程剛接觸時(shí)確實(shí)有點(diǎn)繞,但核心思想很簡(jiǎn)單:別閑著,等的時(shí)候去干點(diǎn)別的。async定義協(xié)程,await讓出控制權(quán),事件循環(huán)負(fù)責(zé)調(diào)度。記住這三件事,你就已經(jīng)入門了。

那些坑,說到底都是忘了“異步代碼里不能有阻塞操作”這條原則。遇到問題的時(shí)候,先檢查是不是用了同步庫(kù),是不是忘記await了,是不是并發(fā)數(shù)設(shè)置得太高。

異步Python這幾年越來越成熟。aiohttp、asyncpg、FastAPI這些生態(tài)組件已經(jīng)足夠支撐大型項(xiàng)目。如果你還在用多線程處理IO密集型任務(wù),不妨試試異步。同樣的機(jī)器資源,異步往往能扛住更高的并發(fā),代碼邏輯也更清晰。

寫異步代碼像是在指揮一支交響樂團(tuán)——每個(gè)樂器都在自己的節(jié)奏上演奏,你不需要盯著每個(gè)人,只需要把握好整體進(jìn)度。一旦適應(yīng)了這種編程思維,你會(huì)發(fā)現(xiàn),原來高并發(fā)也可以寫得這么優(yōu)雅。

以上就是Python異步編程asyncio的核心用法與避坑指南的詳細(xì)內(nèi)容,更多關(guān)于Python異步編程asyncio的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • OpenCV仿射變換的示例代碼

    OpenCV仿射變換的示例代碼

    本文主要介紹了OpenCV仿射變換的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-08-08
  • python中round函數(shù)如何使用

    python中round函數(shù)如何使用

    在本篇文章里小編給大家整理了關(guān)于python的round函數(shù)用法總結(jié)內(nèi)容,需要的朋友們可以學(xué)習(xí)下。
    2020-06-06
  • Python+PyQt5打造一個(gè)Excel批量列處理工具

    Python+PyQt5打造一個(gè)Excel批量列處理工具

    在日常的辦公中,我們經(jīng)常會(huì)遇到需要批量處理Excel文件的情況,今天我們就用 Python + PyQt5 + Pandas 打造一款圖形化的Excel批量列處理工具,不僅支持一鍵拖拽文件,還能動(dòng)態(tài)選擇列名、添加多條處理規(guī)則,幫你徹底告別重復(fù)勞動(dòng)
    2026-04-04
  • PyQt5 實(shí)現(xiàn)給窗口設(shè)置背景圖片的方法

    PyQt5 實(shí)現(xiàn)給窗口設(shè)置背景圖片的方法

    今天小編就為大家分享一篇PyQt5 實(shí)現(xiàn)給窗口設(shè)置背景圖片的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2019-06-06
  • 如何在django中實(shí)現(xiàn)分頁(yè)功能

    如何在django中實(shí)現(xiàn)分頁(yè)功能

    這篇文章主要介紹了如何在django中實(shí)現(xiàn)分頁(yè)功能,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-04-04
  • Python?多處理模塊如何使用示例詳解

    Python?多處理模塊如何使用示例詳解

    這篇文章主要為大家介紹了Python?多處理模塊如何使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09
  • Python Pandas數(shù)據(jù)結(jié)構(gòu)簡(jiǎn)單介紹

    Python Pandas數(shù)據(jù)結(jié)構(gòu)簡(jiǎn)單介紹

    這篇文章主要介紹了Python Pandas數(shù)據(jù)結(jié)構(gòu)簡(jiǎn)單介紹的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-07-07
  • 使用Python+selenium實(shí)現(xiàn)第一個(gè)自動(dòng)化測(cè)試腳本

    使用Python+selenium實(shí)現(xiàn)第一個(gè)自動(dòng)化測(cè)試腳本

    這篇文章主要介紹了使用Python+selenium實(shí)現(xiàn)第一個(gè)自動(dòng)化測(cè)試腳本,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-03-03
  • Python中內(nèi)置數(shù)據(jù)類型list,tuple,dict,set的區(qū)別和用法

    Python中內(nèi)置數(shù)據(jù)類型list,tuple,dict,set的區(qū)別和用法

    這篇文章主要給大家介紹了Python中內(nèi)置數(shù)據(jù)類型list,tuple,dict,set的區(qū)別和用法,都是非?;A(chǔ)的知識(shí),十分的細(xì)致全面,有需要的小伙伴可以參考下。
    2015-12-12
  • 如何使用Python進(jìn)行OCR識(shí)別圖片中的文字

    如何使用Python進(jìn)行OCR識(shí)別圖片中的文字

    這篇文章主要介紹了使用Python進(jìn)行OCR識(shí)別圖片中的文字 ,本文通過實(shí)例代碼加文字說明的形式給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2019-04-04

最新評(píng)論

景谷| 开化县| 界首市| 天全县| 兴业县| 纳雍县| 鹤峰县| 舒兰市| 怀来县| 光山县| 巩留县| 荣昌县| 泽州县| 长岭县| 周至县| 通化市| 烟台市| 安庆市| 黄山市| 周宁县| 东台市| 遂溪县| 高要市| 孟村| 二连浩特市| 龙门县| 民乐县| 赫章县| 衡东县| 疏附县| 乳源| 皋兰县| 竹溪县| 图们市| 门头沟区| 朝阳区| 德惠市| 定日县| 尚义县| 略阳县| 时尚|