在现代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…