從「同步」到「異步」:用 aiohttp 把 Python 網絡 I/O 榨到極致

目錄

一、寫在前面:為什么 IO 是瓶頸

二、同步模型:requests 的憂傷

三、線程池:用并發掩蓋阻塞

四、aiohttp:讓「等待」非阻塞

4.1 安裝與版本約定

4.2 異步客戶端:asyncio + aiohttp

4.3 錯誤處理與超時

4.4 背壓與流量控制

五、異步服務端:用 aiohttp.web 構建 API

六、同步 vs 異步:心智模型對比

七、實戰建議:何時該用 aiohttp

八、結語:讓等待不再是浪費


一、寫在前面:為什么 IO 是瓶頸

在 Python 世界里,CPU 很少成為瓶頸,真正拖慢程序的往往是「等待」。一次 HTTP 請求,服務器把數據發回來的過程中,我們的進程幾乎什么都不做,只是傻傻地等在 recv 上。同步代碼里,這種等待是阻塞的:一個線程卡在那里,別的請求也只能排隊。
于是「異步」登場:在等待期間把 CPU 讓出來給別人用,等數據到了再回來接著干。aiohttp 就是 asyncio 生態里最趁手的 HTTP 客戶端/服務端框架之一。本文不羅列 API,而是帶你從「同步」一步一步走向「異步」,用真實可運行的代碼,體會兩者在吞吐量、代碼結構、心智模型上的差異。


二、同步模型:requests 的憂傷

假設我們要抓取 100 張圖片,每張 2 MB,服務器延遲 200 ms。同步寫法最直觀:

# sync_downloader.py
import requests, time, osURLS = [...]          # 100 條圖片 URL
SAVE_DIR = "sync_imgs"
os.makedirs(SAVE_DIR, exist_ok=True)def download_one(url):resp = requests.get(url, timeout=30)fname = url.split("/")[-1]with open(os.path.join(SAVE_DIR, fname), "wb") as f:f.write(resp.content)return len(resp.content)def main():start = time.perf_counter()total = 0for url in URLS:total += download_one(url)elapsed = time.perf_counter() - startprint(f"sync 下載完成:{len(URLS)} 張,{total/1024/1024:.1f} MB,耗時 {elapsed:.2f}s")if __name__ == "__main__":main()

在我的 100 M 帶寬機器上跑,耗時 22 秒。瓶頸顯而易見:每次網絡 IO 都阻塞在 requests.get,一個線程只能串行干活。


三、線程池:用并發掩蓋阻塞

同步代碼并非無可救藥,把阻塞 IO 丟進線程池,依舊能提速。concurrent.futures.ThreadPoolExecutor 就是 Python 標準庫給的「急救包」:

# thread_pool_downloader.py
from concurrent.futures import ThreadPoolExecutor, as_completed
import requests, time, osURLS = [...]
SAVE_DIR = "thread_imgs"
os.makedirs(SAVE_DIR, exist_ok=True)def download_one(url):resp = requests.get(url, timeout=30)fname = url.split("/")[-1]with open(os.path.join(SAVE_DIR, fname), "wb") as f:f.write(resp.content)return len(resp.content)def main():start = time.perf_counter()total = 0with ThreadPoolExecutor(max_workers=20) as pool:futures = [pool.submit(download_one, u) for u in URLS]for f in as_completed(futures):total += f.result()elapsed = time.perf_counter() - startprint(f"線程池下載完成:{len(URLS)} 張,{total/1024/1024:.1f} MB,耗時 {elapsed:.2f}s")if __name__ == "__main__":main()

20 條線程并行后,耗時驟降到 2.7 秒。但線程有代價:每條約 8 MB 棧內存,20 條就 160 MB,且受到 GIL 限制,在 CPU 密集任務里會互相踩踏。對網絡 IO 而言,線程池屬于「曲線救國」,真正原生的解決方案是「異步協程」。


四、aiohttp:讓「等待」非阻塞

4.1 安裝與版本約定
pip install aiohttp==3.9.1  # 文章編寫時的穩定版
4.2 異步客戶端:asyncio + aiohttp

把剛才的下載邏輯用 aiohttp 重寫:

# async_downloader.py
import asyncio, aiohttp, time, osURLS = [...]
SAVE_DIR = "async_imgs"
os.makedirs(SAVE_DIR, exist_ok=True)async def download_one(session, url):async with session.get(url) as resp:content = await resp.read()fname = url.split("/")[-1]with open(os.path.join(SAVE_DIR, fname), "wb") as f:f.write(content)return len(content)async def main():start = time.perf_counter()conn = aiohttp.TCPConnector(limit=20)  # 限制并發連接數timeout = aiohttp.ClientTimeout(total=30)async with aiohttp.ClientSession(connector=conn, timeout=timeout) as session:tasks = [download_one(session, u) for u in URLS]results = await asyncio.gather(*tasks)total = sum(results)elapsed = time.perf_counter() - startprint(f"async 下載完成:{len(URLS)} 張,{total/1024/1024:.1f} MB,耗時 {elapsed:.2f}s")if __name__ == "__main__":asyncio.run(main())

同一臺機器,耗時 2.4 秒。表面上和線程池差不多,但內存占用僅 30 MB,且沒有線程切換的上下文開銷。
關鍵點在于 await resp.read():當數據尚未抵達,事件循環把控制權交出去,CPU 可以處理別的協程;數據到了,事件循環恢復這條協程,繼續執行。整個過程是「單線程并發」。

4.3 錯誤處理與超時

網絡請求總要面對超時、重試。aiohttp 把異常體系做得非常「async 友好」:

from aiohttp import ClientErrorasync def download_one(session, url):try:async with session.get(url) as resp:resp.raise_for_status()return await resp.read()except (ClientError, asyncio.TimeoutError) as e:print(f"下載失敗: {url} -> {e}")return 0
4.4 背壓與流量控制

并發不是越高越好。若不加限制,瞬間上千條 TCP 連接可能把目標服務器打掛。aiohttp 提供了 TCPConnector(limit=...)asyncio.Semaphore 兩種手段。下面演示自定義信號量:

sem = asyncio.Semaphore(20)async def download_one(session, url):async with sem:  # 同一時刻最多 20 條協程進入...

五、異步服務端:用 aiohttp.web 構建 API

異步不僅用于客戶端,服務端同樣受益。下面寫一個極簡「圖床」服務:接收 POST 上傳圖片,返回 URL。

# async_server.py
import asyncio, aiohttp, aiohttp.web as web, uuid, osUPLOAD_DIR = "uploads"
os.makedirs(UPLOAD_DIR, exist_ok=True)async def handle_upload(request):reader = await request.multipart()field = await reader.next()if field.name != "file":return web.Response(text="missing field 'file'", status=400)filename = f"{uuid.uuid4().hex}.jpg"with open(os.path.join(UPLOAD_DIR, filename), "wb") as f:while chunk := await field.read_chunk():f.write(chunk)url = f"http://{request.host}/static/{filename}"return web.json_response({"url": url})app = web.Application()
app.router.add_post("/upload", handle_upload)
app.router.add_static("/static", UPLOAD_DIR)if __name__ == "__main__":web.run_app(app, host="0.0.0.0", port=8000)

單進程單線程即可支撐數千并發上傳。得益于 asyncio,磁盤 IO 不會阻塞事件循環;若換成同步框架(Flask + gunicorn 同步 worker),每個上傳都要獨占線程,高并發下線程池瞬間耗盡。


六、同步 vs 異步:心智模型對比

維度同步線程池異步
并發單位線程線程協程
內存開銷極低
阻塞行為阻塞阻塞非阻塞
代碼風格線性線性async/await
調試難度


同步代碼像讀小說,一行一行往下看;異步代碼像翻撲克牌,事件循環決定哪張牌先被翻開。對初學者而言,最困惑的是「函數一半跑一半掛起」的感覺。解決方法是:

  1. 把每個 await 當成「可能切換點」,在它之前保證數據處于自洽狀態。

  2. asyncio.create_task 而不是裸 await,避免順序陷阱。

  3. 日志里打印 asyncio.current_task().get_name() 追蹤協程。


七、實戰建議:何時該用 aiohttp

  1. 客戶端高并發抓取:爬蟲、壓測、批量 API 調用,aiohttp + asyncio 是首選。

  2. 服務端 IO 密集:網關、代理、WebHook、長連接推送。

  3. 混合場景:若既有 CPU 密集又有 IO 密集,可用 asyncio.to_thread 把 CPU 任務丟進線程池,主協程繼續處理網絡。

不適用場景:

  • CPU 密集計算(如圖像處理)應放到進程池或外部服務;

  • 低延遲、小并發內部 RPC,同步 gRPC 可能更簡單。


八、結語:讓等待不再是浪費

從最早的串行下載,到線程池并發,再到 aiohttp 的協程狂歡,我們見證了「等待」如何被一點點榨干價值。掌握異步不是追逐時髦,而是回歸本質:CPU 很貴,別讓它在 IO 上睡覺。
下次當你寫下 await session.get(...) 時,不妨想象事件循環在背后穿梭:它像一位老練的調度員,把每一個「等待」的空檔,填得滿滿當當。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/92134.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/92134.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/92134.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

MySQL 在麒麟系統上部署使用 + DBeaver 遠程連接 + SQL 數據導入完整流程

🚀 MySQL 在麒麟系統上部署使用 DBeaver 遠程連接 SQL 數據導入完整流程適用于國產操作系統(如:麒麟 / 統信 / Ubuntu)和 MySQL 8.0。包含遠程配置、授權連接、SQL 導入、DBeaver連接配置等常見問題解決方案。📦 環境…

C語言-指針初級(指針定義、指針的作用、指針的計算、野指針、懸空指針、void類型指針)

本章概述思維導圖:C語言指針指針是C語言中最強大但也最容易混淆的特性之一。它提供了直接操作內存地址的能力,使得C語言具有高效性和靈活性。下面我將詳細介紹C語言指針的各個方面。指針定義指針的本質:指針是一個變量,其值為另一…

具身智能VLA困于“數據泥潭”,人類活動視頻數據是否是“破局之鑰”?

前言盡管當前的視覺-語言-動作(VLA)模型已展現出顯著進展,但其在新場景和與復雜物體交互中的性能會顯著下降,在遵循指令方面落后于像LLaVA 這樣的大型多模態模型(LMM)。這種局限性源于現有VLA模型對存在固有…

CIO如何規劃企業BI分析指標體系 —— 從經營出發到績效管理

如果你是一家企業的CIO,要啟動一個商業智能BI項目,負責規劃整個項目的商業智能BI分析內容,你該如何入手準備?要有什么樣的思路。如果是管理層、老板還不能清晰認識到商業智能BI的價值,也提不出很清晰的需求&#xff0c…

go學習筆記:panic是什么含義

anic 是 Go 語言中的一種運行時錯誤處理機制,用于處理程序中的異常情況。 基本含義 panic 會: 立即停止當前函數的執行 開始執行 defer 函數(如果有的話) 向上傳播到調用棧,逐層執行 defer 如果到達 main 函數&am…

OpenLayers 入門指南【五】:Map 容器

文章目錄 一、Map 對象核心參數 1. target 2. view 3. layers 4. controls 5. interactions 6. 其他重要參數 二、Map 對象常用方法 1. 圖層管理 2. 控件管理 3. 交互管理 4. 視圖與坐標操作 5. 事件監聽 6. 覆蓋物管理 7. 其他 三、總結 上一章節中我們通過修改OlMap.vue組件已…

關稅戰火中的技術方舟:新西蘭證券交易所的破局之道 ——從15%關稅沖擊到跨塔斯曼結算聯盟,解碼下一代交易基礎設施

一、今日焦點:全球關稅震蕩與新西蘭的“技術自衛” 1. 特朗普關稅大限落地,新西蘭啟動緊急游說 2025年8月1日,美國總統特朗普正式簽署行政令,對貿易順差國征收最低15%基準關稅。新西蘭貿易部長緊急聲明:“將提出有力證…

windows內核研究(軟件調試-軟件斷點)

軟件調試軟件斷點調試的本質是什么?就是在被調試程序中觸發異常,然后被調試程序就會向_DEBUG_OBJECT結構體添加調試事件,這里我們調試器就接管這個異常了(調試的過程就是異常處理的過程) 軟件斷點 在x64dbg中通過快捷鍵…

HarmonyOS】鴻蒙應用開發中常用的三方庫介紹和使用示例

🌟 鴻蒙應用開發常用三方庫指南(2025 最新版)適用版本:HarmonyOS NEXT / API 12 參考來源:HarmonyOS 三方庫中心 截止至 2025 年 8 月 1 日,本文整理了當前社區中下載量高、穩定性強、生態完善的熱門三方庫…

【通識】C Sharp

1. 使用 \p{名稱}構造匹配Unicode常規類別(該示例為Pd或“標點、短劃線”類別)和命名塊(IsGreek和IsBsicLatin命名塊) using System; using system.Text.RegularExpressions; public class Example {public static void main() {s…

國內首個開源SCA社區——OpenSCA開源社區

OpenSCA開源社區成果說明項目背景智能時代,軟件定義一切。隨著開發模式的敏捷化轉型,開源代碼在軟件制品中的占比越來越大,開源軟件已然成為軟件供應鏈的重要組成部分。由于其特殊性,開源代碼的引入增加了軟件應用的風險面&#x…

超聚變:智能體時代,AI原生重構城企數智化基因

2025 世界人工智能大會(WAIC)世博展覽館內,超聚變展臺前人頭攢動,其展示的AI落地全棧解決方案及上百個AI應用場景吸引了眾多參觀者駐足觀看。這是今年WAIC大會火爆的一角,更是當下AI應用爆發的一個縮影。當人工智能發展…

Traccar:開源GPS追蹤系統的核心價值與技術全景

Traccar:開源GPS追蹤系統的核心價值與技術全景 —— 從設備兼容到企業級定位管理的開源實踐 一、項目定位:多場景定位管理的開源基石 Traccar是一個高擴展性的開源GPS追蹤平臺,支持全球超過200種通信協議與2000款GPS設備(包括車…

編程與數學 03-002 計算機網絡 20_計算機網絡課程實驗與實踐

編程與數學 03-002 計算機網絡 20_計算機網絡課程實驗與實踐一、實驗環境搭建(一)使用模擬器(如Cisco Packet Tracer)搭建網絡實驗環境(二)實驗設備的配置與連接二、基礎網絡實驗(一&#xff09…

15個命令上手Linux!

1、id,顯示當前登錄系統的用戶信息2、pwd,顯示當前工作目錄的絕對路徑3、ls,顯示當前目錄下的內容(ls -r:按反向順序列出內容,ls -l:以詳細列表形式顯示)4、cd,切換工作目…

MongoDB分片技術實現

MongoDB分片技術實現概述MongoDB分片(Sharding)是MongoDB的水平擴展解決方案,通過將數據分布到多個分片(shard)上來處理大數據量和高吞吐量的需求。MongoDB分片架構1. 分片集群組件# MongoDB分片集群架構 version: 3.8…

Python開發環境PyCharm下載與安裝

python下載 python下載地址: Download Python | Python.org 上面的下載速度慢的話,用下面的地址下載(window): https://download.csdn.net/download/liangmengbk/91580033 PyCharm下載 PyCharm下載地址&#xff1a…

汽車供應鏈PPAP自動化審核指南:如何用AI實現規則精準匹配與文件智能校驗

在汽車行業質量管理的核心環節,PPAP(生產件批準程序)審核長期困擾著供應商與主機廠。 隨著IATF 16949等標準持續升級、新能源零件復雜度激增,傳統人工審核模式正面臨系統性挑戰。 行業數據顯示,超過70%的SQE&#xf…

正則表達式在js中的應用

正則表達式在 JavaScript 中的應用非常廣泛,尤其是在字符串處理和驗證方面。以下是一些常見的正則表達式方法及其應用示例,包括 .test() 方法。 1. .test() 方法 .test() 方法用于測試一個字符串是否匹配正則表達式。如果匹配,返回 true&…

Rust視頻處理開源項目精選

Rust視頻處理開源項目精選 基于Rust實現的視頻處理示例 以下是一些基于Rust實現的視頻處理或多媒體相關的開源項目或示例,涵蓋編解碼、流媒體、分析工具等方向,可作為實際開發參考: 視頻編解碼與處理 rav1e:Rust編寫的AV1視頻編碼器,高性能且內存安全,適合研究視頻壓縮…