Agent實戰教程:深度解析async異步編程在Langgraph中的性能優化

在現代Python開發中,異步編程已經成為提高程序性能的重要手段,特別是在處理網絡請求、數據庫操作或AI模型調用等耗時操作時。本文將通過實際的LangGraph 示例,深入解析async的真正作用,并揭示一個常見誤區:為什么異步順序執行與同步執行時間相近?

async的核心作用

async的主要價值在于創建異步編程環境,讓程序在等待耗時操作時不被阻塞,從而提高執行效率。但是,很多開發者對異步編程存在一個根本性的誤解。

常見誤區:async ≠ 自動加速

許多人認為只要在函數前加上async,程序就會自動變快。這是錯誤的

讓我們通過一個LangGraph的實際例子來說明:

from langchain.chat_models import init_chat_model
from langgraph.graph import MessagesState, StateGraphfrom dotenv import load_dotenv
load_dotenv()
# 初始化 LLM 模型
llm = ChatDeepSeek(model="deepseek-chat")# 異步節點定義
async def async_node(state: MessagesState): new_message = await llm.ainvoke(state["messages"]) return {"messages": [new_message]}builder = StateGraph(MessagesState).add_node(async_node).set_entry_point("node")
graph = builder.compile()

完整的性能對比示例

以下是一個可以完整運行的性能測試示例:

import asyncio
import time
from langchain.chat_models import init_chat_model
from langgraph.graph import MessagesState, StateGraphfrom dotenv import load_dotenv
load_dotenv()
# 初始化 LLM 模型
llm = ChatDeepSeek(model="deepseek-chat")# 同步版本的節點
def sync_node(state: MessagesState):"""同步版本:會阻塞等待"""new_message = llm.invoke(state["messages"])return {"messages": [new_message]}# 異步版本的節點
async def async_node(state: MessagesState):"""異步版本:可以并發執行"""new_message = await llm.ainvoke(state["messages"])return {"messages": [new_message]}# 創建同步圖
sync_builder = StateGraph(MessagesState).add_node("node", sync_node).set_entry_point("node")
sync_graph = sync_builder.compile()# 創建異步圖
async_builder = StateGraph(MessagesState).add_node("node", async_node).set_entry_point("node")
async_graph = async_builder.compile()# 測試消息
messages = [{"role": "user", "content": "你好,請介紹一下自己"},{"role": "user", "content": "請解釋一下什么是人工智能"},{"role": "user", "content": "給我講個笑話吧"},{"role": "user", "content": "請推薦幾本好書"},{"role": "user", "content": "今天天氣怎么樣?"}
]def test_sync_sequential():"""測試同步順序執行"""print("同步順序執行測試...")start_time = time.time()results = []for i, msg in enumerate(messages):print(f"  處理消息 {i+1}/{len(messages)}...")result = sync_graph.invoke({"messages": [msg]})results.append(result)end_time = time.time()duration = end_time - start_timeprint(f"同步執行完成,總耗時: {duration:.2f} 秒")return results, durationasync def test_async_sequential():"""測試異步順序執行"""print("異步順序執行測試...")start_time = time.time()results = []for i, msg in enumerate(messages):print(f"  處理消息 {i+1}/{len(messages)}...")result = await async_graph.ainvoke({"messages": [msg]})results.append(result)end_time = time.time()duration = end_time - start_timeprint(f"異步順序執行完成,總耗時: {duration:.2f} 秒")return results, durationasync def test_async_concurrent():"""測試異步并發執行"""print("異步并發執行測試...")start_time = time.time()# 創建所有任務tasks = []for i, msg in enumerate(messages):print(f"  啟動任務 {i+1}/{len(messages)}...")task = async_graph.ainvoke({"messages": [msg]})tasks.append(task)# 并發執行所有任務print("  所有任務并發運行中...")results = await asyncio.gather(*tasks)end_time = time.time()duration = end_time - start_timeprint(f"異步并發執行完成,總耗時: {duration:.2f} 秒")return results, durationasync def main():"""主函數:運行所有測試"""print("=" * 60)print("LangGraph 異步 vs 同步性能測試")print("=" * 60)print(f"測試場景:處理 {len(messages)} 個 LLM 請求")print()# 1. 同步順序執行sync_results, sync_time = test_sync_sequential()print()# 2. 異步順序執行async_seq_results, async_seq_time = await test_async_sequential()print()# 3. 異步并發執行async_con_results, async_con_time = await test_async_concurrent()print()# 性能對比分析print("=" * 60)print("性能對比分析")print("=" * 60)print(f"同步順序執行:     {sync_time:.2f} 秒")print(f"異步順序執行:     {async_seq_time:.2f} 秒")print(f"異步并發執行:     {async_con_time:.2f} 秒")print()# 計算性能提升if async_con_time > 0:speedup_vs_sync = sync_time / async_con_timespeedup_vs_async_seq = async_seq_time / async_con_timeprint("性能提升:")print(f"異步并發 vs 同步順序: {speedup_vs_sync:.1f}x 倍速提升")print(f"異步并發 vs 異步順序: {speedup_vs_async_seq:.1f}x 倍速提升")# 運行測試
if __name__ == "__main__":asyncio.run(main())

三種執行方式的性能對比

1. 同步順序執行

def test_sync_sequential():results = []for msg in messages:result = sync_graph.invoke({"messages": [msg]})results.append(result)return results# 執行時間線:
# [請求1---等待---響應1] [請求2---等待---響應2] [請求3---等待---響應3] ...
# 總耗時:約 10-15 秒(5個請求 × 每個2-3秒)

2. 異步順序執行

async def test_async_sequential():results = []for msg in messages:result = await async_graph.ainvoke({"messages": [msg]})  # 還是逐個等待results.append(result)return results# 執行時間線:
# [請求1---等待---響應1] [請求2---等待---響應2] [請求3---等待---響應3] ...
# 總耗時:約 10-15 秒(與同步執行相近)

3. 異步并發執行

async def test_async_concurrent():# 關鍵:同時啟動所有任務tasks = [async_graph.ainvoke({"messages": [msg]}) for msg in messages]# 并發執行所有任務results = await asyncio.gather(*tasks)return results# 執行時間線:
# [請求1---等待---響應1]
# [請求2---等待---響應2]  ← 同時進行
# [請求3---等待---響應3]  ← 同時進行
# [請求4---等待---響應4]  ← 同時進行
# [請求5---等待---響應5]  ← 同時進行
# 總耗時:約 2-3 秒(接近單個請求時間)

為什么異步順序執行時間相近?

這個現象困惑了很多開發者。讓我們深入分析原因:

控制權的概念

在異步編程中,控制權指的是CPU當前正在執行哪段代碼的決定權。

同步執行中的控制權
def sync_function():print("開始")result = llm.invoke(messages)  # CPU 在這里"卡住"等待print("結束")return result# 執行流程:
# 1. CPU 執行 print("開始")
# 2. CPU 調用 llm.invoke()
# 3. CPU 完全停止,等待網絡響應(2-3秒)
# 4. 收到響應后,CPU 繼續執行 print("結束")

在步驟3中,CPU被完全占用但什么都不做,這就是"阻塞"。

異步執行中的控制權轉移
async def async_function():print("開始")result = await llm.ainvoke(messages)  # 讓出控制權print("結束")return result# 執行流程:
# 1. CPU 執行 print("開始")
# 2. CPU 調用 llm.ainvoke()
# 3. 遇到 await,CPU 說:"我先去做別的事,響應來了再叫我"
# 4. CPU 可以執行其他任務
# 5. 網絡響應到達,CPU 重新獲得控制權
# 6. CPU 繼續執行 print("結束")

關鍵洞察:讓出控制權 ≠ 時間節省

# 異步但沒有性能提升(順序執行)
for msg in messages:result = await process_message(msg)  # 還是一個接一個等待# 異步真正的優勢(并發執行)
tasks = [process_message(msg) for msg in messages]
results = await asyncio.gather(*tasks)  # 同時處理所有

異步順序執行時間相近的原因

  1. 都是順序執行:兩種方式都是"處理完第一個請求,再處理第二個"
  2. 等待時間相同:每個LLM調用的網絡延遲和處理時間是一樣的
  3. 沒有并發優勢:異步順序執行沒有利用異步的核心優勢——并發

實際運行和測試

將上述代碼保存為 async_test.py,運行后會看到類似輸出:

============================================================
🧪 LangGraph 異步 vs 同步性能測試
============================================================
📝 測試場景:處理 5 個 LLM 請求🔄 同步順序執行測試...處理消息 1/5...處理消息 2/5...處理消息 3/5...處理消息 4/5...處理消息 5/5...
? 同步執行完成,總耗時: 148.37 秒? 異步順序執行測試...處理消息 1/5...處理消息 2/5...處理消息 3/5...處理消息 4/5...處理消息 5/5...
? 異步順序執行完成,總耗時: 147.72 秒🚀 異步并發執行測試...啟動任務 1/5...啟動任務 2/5...啟動任務 3/5...啟動任務 4/5...啟動任務 5/5...🔥 所有任務并發運行中...
? 異步執行完成,總耗時: 67.24 秒============================================================
📊 性能對比分析
============================================================
同步順序執行:     148.37 秒
異步順序執行:     147.72 秒
異步并發執行:     67.24 秒🎯 性能提升:
異步并發 vs 同步順序: 2.2x 倍速提升
異步并發 vs 異步順序: 2.2x 倍速提升💡 關鍵發現:
? 異步并發執行可以顯著減少總耗時
? 當有多個獨立的 LLM 調用時,并發執行效果最明顯
? 異步順序執行與同步執行時間相近(都是逐個等待)
? 實際加速比取決于網絡延遲和 LLM 響應時間

實際應用指導

何時使用異步?

適合使用異步的場景:

  • 多個獨立的網絡請求(如批量API調用)
  • 并發的數據庫查詢
  • 同時處理多個用戶請求
  • I/O密集型任務

不適合使用異步的場景:

  • CPU密集型計算
  • 必須順序執行的依賴任務
  • 簡單的單次操作

最佳實踐

# 錯誤用法:異步但無性能提升
async def bad_example():result1 = await api_call_1()result2 = await api_call_2()  # 依賴result1result3 = await api_call_3()  # 依賴result2return [result1, result2, result3]# 改進:部分并發
async def better_example():# 可以并發的部分task1 = api_call_1()task2 = independent_api_call()result1, result2 = await asyncio.gather(task1, task2)# 依賴前面結果的部分result3 = await api_call_3(result1)return [result1, result2, result3]# 最佳:完全并發(當任務獨立時)
async def best_example():tasks = [api_call_1(),api_call_2(),api_call_3(),api_call_4(),api_call_5()]results = await asyncio.gather(*tasks)return results

總結

  1. async的真正價值:不在于讓單個任務變快,而在于讓多個任務可以同時進行
  2. 異步順序執行時間相近:因為還是逐個等待,沒有發揮并發優勢
  3. 性能提升的關鍵:使用asyncio.gather()或類似機制實現真正的并發
  4. 實際應用:在設計異步程序時,要識別哪些任務可以并發執行

異步編程是一個強大的工具,但只有正確使用才能發揮其真正的威力。記住:異步的魅力不在于等待得更快,而在于可以同時等待多件事情

延伸思考:在你的項目中,有哪些場景可以從順序執行改為并發執行?試著識別那些相互獨立的異步操作,這通常是性能優化的黃金機會。

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

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

相關文章

coalesce在sql中什么作用

COALESCE?是SQL中的一個函數,用于返回參數列表中的第一個非空值,若所有參數均為NULL則返回NULL,常用于處理數據中的空值情況。 ?核心功能與語法? COALESCE函數的基本語法為:COALESCE(expression1, expression2, ..., express…

【Rust】 6. 字符串學習筆記

一、Rust 字符串概述 Rust 字符串是 UTF-8 編碼的文本序列,提供兩種主要類型: &str - 字符串切片(通常作為引用出現)String - 動態可變的、擁有所有權的字符串 二、字符串字面量 (&str) 編譯時已知大小,靜態分…

達夢數據庫-數據文件 (二)

達夢數據庫-數據文件(二)-自動監控達夢數據庫表空間使用率的 Shell 腳本 自動監控達夢數據庫表空間使用率的 Shell 腳本,支持: ? 實時計算每個表空間的使用率? 設置閾值告警(如 >80%)? 支持郵件告警&…

如何用 Android 平臺開發第一個 Kotlin 小程序

安裝開發環境下載并安裝最新版 Android Studio(官方 IDE),安裝時勾選 Kotlin 插件。確保 JDK 版本為 11 或更高。創建新項目打開 Android Studio 選擇 File > New > New Project,選擇 Empty Activity 模板。在配置界面中&am…

Java常用工具類

異常 (Exception)。程序世界并非總是完美的,異常處理機制就是Java為我們提供的優雅應對錯誤的解決方案。一、為什么需要異常處理?—— 從現實世界說起 想象一下現實生活中的場景: 開車上班:你計劃開車去公司(正常流程&…

AWS亞馬遜云賬號注冊指南

AWS是全球領先的云計算平臺,提供廣泛的云服務。賬號注冊是開端,不管是用來學習、搭建個人項目,還是公司項目部署上線需要,都需要進行這一步。提醒:在使用賬戶之前,必須要綁定國際的信用卡;通過我…

云計算學習100天-第31天

Keepalived概念keepalived 是Linux下一個輕量級的高可用解決方案主要是通過虛擬路由冗余協議(VRRP)來實現高可用功能Virtual Router Redundancy Protocol起初就是為了補充LVS功能而設計的,用于監控LVS集群內后端真實服務器狀態后來加入了VRRP的功能,它出…

2025年視覺、先進成像和計算機技術論壇(VAICT 2025)

會議簡介 作為人工智能大數據創新發展論壇的重要分論壇,2025年視覺、先進成像和計算機技術論壇聚焦人工智能感知世界的核心前沿,將于2025年9月18-20日在中國廣州廣東科學館舉行。 視覺與成像技術是智能系統理解環境的關鍵,計算機技術則…

MySQL 與 ClickHouse 深度對比:架構、性能與場景選擇指南

🌟 引言:數據時代的引擎之爭 在當今數據驅動的企業環境中,選擇合適的數據庫引擎成為架構設計的關鍵決策。想象這樣一個場景:特斯拉的實時車況分析系統需要在毫秒級延遲下處理數百萬輛汽車的傳感器數據,而某電商平臺的訂…

閉包與內存泄漏:深度解析與應對策略

在 JavaScript 編程中,閉包是一個強大且常用的特性,但如果使用不當,可能會引發內存泄漏問題,影響程序性能甚至導致頁面卡頓。本文將深入剖析閉包導致內存泄漏的原理,結合實例講解,并給出切實可行的避免方法…

open webui源碼分析12-Pipeline

Pipeline是 Open WebUI 的一項創新,它 為任何支持 OpenAI API 規范的 UI 客戶端帶來了模塊化、可定制的工作流 —— 甚至更多功能!只需幾行代碼,你就能輕松擴展功能、集成自己的專有邏輯并創建動態工作流。 當你處理計算密集型任務&#xff0…

深入解析 Chromium Mojo IPC:跨進程通信原理與源碼實戰

在現代瀏覽器架構中,多進程設計已經成為標配。Chromium 瀏覽器作為典型的多進程瀏覽器,其瀏覽器進程(Browser Process)、渲染進程(Renderer Process)、GPU 進程、Utility 進程等之間的通信,依賴…

【自動化測試】測試分類概述-初步接觸自動化測試

🔥個人主頁: 中草藥 🔥專欄:【Java】登神長階 史詩般的Java成神之路 測試分類 了解各種各樣的測試方法分類,不是為了墨守成規按照既定方法區測試,而是已了解思維為核心,并了解一些專業名詞 根…

【Python辦公】快速比較Excel文件中任意兩列數據的一致性

目錄 專欄導讀 項目背景 技術選型 核心技術棧 選型理由 功能特性 ?? 核心功能 ?? 輔助功能 架構設計 整體架構 設計模式 核心代碼解析 1. 類初始化和UI設置 2. 文件選擇和數據加載 3. 數據比較核心算法 4. 結果導出功能 界面設計詳解 布局結構 UI組件選擇 性能優化 1. 內存…

nginx的誕生背景、核心優勢、與 Apache 的對比

下面用“3 個 1 分鐘”幫你快速建立 Nginx 的整體印象: 1 分鐘了解它為何誕生,1 分鐘看懂它的 5 大核心優勢,再花 1 分鐘搞清和 Apache 的關鍵差異。誕生背景(2002-2004) ? 作者:俄羅斯系統工程師 Igor Sy…

算法題打卡力扣第169題:多數元素(easy)

文章目錄題目描述解法一:暴力解解法二 排序法解法三:Boyer-Moore 投票算法 (最優解)題目描述 解法一:暴力解 定義一個數組C用于存放nums數組中每個數出現的次數,然后再遍歷C,判斷C【i】是否大于? n/2 ?,…

A6.0:PCB的設計流程

第一步:導入網表第二步:結構導入和板框定義1.導入結構文件:加載DXF格式的機械結構圖(含板框、定位孔、限高區),確保元件布局符合物理約束。2.固定器件預放置:將接插件、按鍵、散熱器等結構敏感元件鎖定到指定位置,避免后期調整沖突…

深度學習在金融訂單簿分析與短期市場預測中的應用

金融訂單簿記錄了市場上買賣雙方的委托訂單信息,包括價格、數量、訂單類型等關鍵要素。其數據具有以下特點: 高頻性:訂單在極短時間內不斷產生與變化,數據更新速度極快,每秒可能產生大量新訂單。序列性:訂單…

C++基礎算法——貪心算法

思想&#xff1a;總是做出在當前看來是最好的選擇 例題一、排隊打水問題 n個人&#xff0c;r個水龍頭&#xff0c;花費時間最少的安排&#xff1f;&#xff08;包含等待時間&#xff09; #include<iostream> #include <bits/stdc.h> using namespace std; int ma…

事務和鎖(進階)

事務和鎖&#xff08;進階&#xff09;一.回顧事務1.什么是事務2 為什么要使用事務3 怎么使用事務二.InnoDB和ACID模型三. 如何實現原子性四.如何實現持久性五.隔離性實現原理1.事務的隔離性2.事務的隔離級別3.鎖1&#xff09;鎖信息2&#xff09; 共享鎖和獨占鎖-Shared and E…