掌握 Python 异步编程与 asyncio 库 —— 提升高并发编程效率

掌握 Python 异步编程与 asyncio 库 —— 提升高并发编程效率

Python 中的异步编程,尤其是 asyncio 库,是实现高性能应用的强大工具。它可以让代码非阻塞地运行多个任务,非常适合高并发的场景,比如处理大量 I/O 操作或 Web 请求。下面,我们将从基础概念出发,一步步深入,最终带你写出流畅高效的异步代码。


1. 为什么选择异步编程?

异步编程可以显著提升性能,特别是在处理大量 I/O 操作时。传统的同步编程要求每个操作依次完成,意味着在等待一个文件读写或网络响应时,程序会停在那里浪费时间。而异步编程可以在等待时接着处理其他任务,充分利用 CPU,提升整体效率。

比如:假设我们有一个应用需要处理成百上千个 Web 请求,异步编程可以让应用程序一边等待网络响应,一边处理其他请求,而不用卡在那里傻等。这样就可以大大提高系统的吞吐量和响应速度。


2. 同步 vs 异步编程:概念和对比

  • 同步编程:传统上,Python 是同步执行的,也就是按顺序运行每一行代码。如果有个地方需要等待,比如等待网络请求返回,那么后面的代码会一直卡住,直到请求结束。

  • 异步编程:异步编程允许程序在等待的过程中继续运行其他代码。你可以把它想象成一个「会 multitask 的人」,只要有个地方需要等,他会暂时放下这部分,接着做其他事。这个特性在需要并发处理的场景中非常高效。


3. Python 异步编程基础:事件循环、协程和任务

事件循环(Event Loop)是什么?

事件循环是 asyncio 的核心。简单来说,它是一个调度器,负责管理和调度所有的异步任务。所有的协程、任务、I/O 操作等都会提交给事件循环,由它来决定什么先执行,什么后执行。

在 Python 中,你可以使用 asyncio.run() 来启动一个事件循环,并运行你的异步代码。

协程(Coroutine)是什么?

协程是异步代码的基本单元。它有点像普通的函数,但不同的是,它的执行可以暂停(使用 await 关键字),然后在需要的时候继续运行。这样,程序可以切换到其他任务上,不用一直等着协程执行完毕。

在 Python 中,协程通过 async def 定义,比如:

import asyncioasync def my_coroutine():print("这是一个协程")await asyncio.sleep(1)  # 模拟I/O等待print("协程执行完毕")

任务(Task)是什么?

任务是协程的一种包装形式,它是事件循环中的实际执行单元。通过 asyncio.create_task(),可以把协程包装成任务,并提交到事件循环中。

任务和协程的区别是:协程只是一个潜在的任务,只有被事件循环执行时才会变成真正的任务。


4. asyncio 的基本用法

我们来看看 asyncio 中几个核心的用法。

启动事件循环并运行协程

import asyncioasync def main():print("启动主协程")await asyncio.sleep(2)print("主协程完成")# 使用 asyncio.run() 启动事件循环并运行主协程
asyncio.run(main())

上面的代码创建了一个事件循环,运行了 main() 协程。await asyncio.sleep(2) 用来模拟一个耗时操作(比如网络请求),程序会等待 2 秒,然后打印完成消息。

创建并发任务

asyncio.create_task() 是让多个任务并发执行的关键。例如,我们同时启动两个任务,看看它们如何并行执行:

import asyncioasync def download_data(name, delay):print(f"{name} 开始下载数据")await asyncio.sleep(delay)print(f"{name} 数据下载完成")async def main():# 创建两个并发任务task1 = asyncio.create_task(download_data("任务1", 2))task2 = asyncio.create_task(download_data("任务2", 1))# 等待所有任务完成await task1await task2asyncio.run(main())

输出顺序会像这样:

任务1 开始下载数据
任务2 开始下载数据
任务2 数据下载完成
任务1 数据下载完成

在这里,任务2比任务1先完成,因为它的 await asyncio.sleep(1) 延迟时间更短。这种并发特性让我们可以高效利用时间。


5. 实战:创建一个异步 Web 爬虫

我们来写个例子:用 asyncio 实现一个异步爬虫。这个小爬虫会访问多个 URL,并行抓取数据。

首先,请确保你已安装 aiohttp和bs4 库:

pip install aiohttp
pip install bs4

接着,我们编写一个可以抓取网页标题的异步爬虫:

import asyncio
import aiohttp
from bs4 import BeautifulSoupasync def fetch_title(url):async with aiohttp.ClientSession() as session:try:async with session.get(url) as response:if response.status == 200:# 读取网页内容html = await response.text()# 解析网页标题soup = BeautifulSoup(html, 'html.parser')title = soup.title.string if soup.title else "无标题"print(f"{url} 的标题是: {title}")else:print(f"抓取 {url} 失败,状态码:{response.status}")except Exception as e:print(f"抓取 {url} 时发生错误: {e}")async def main():urls = ["https://www.taobao.com","https://www.python.org","https://www.jd.com"]# 创建并发任务tasks = [asyncio.create_task(fetch_title(url)) for url in urls]# 等待所有任务完成await asyncio.gather(*tasks)# 运行主协程
asyncio.run(main())

在这个例子中,asyncio.gather(*tasks) 可以将多个任务打包,等待所有任务都完成。这种并发方式让每个 URL 的抓取互不影响。


6. 高级用法:异步上下文管理器、迭代器和队列

异步上下文管理器

异步上下文管理器是一种用于管理异步资源的工具。它的主要作用是在协程中正确地处理需要初始化和清理的操作,例如打开和关闭文件、数据库连接、网络套接字等。与同步上下文管理器类似,异步上下文管理器使用 async with 语法来确保在使用资源时进行必要的管理,并且可以保证无论操作成功与否,资源都会被正确释放。

异步上下文管理器通常用于需要异步初始化和清理的场景,尤其是在高并发或长时间运行的应用中。例如,数据库连接、文件流、网络连接等资源的管理。

如何实现异步上下文管理器

要实现一个异步上下文管理器,需要在类中定义两个特殊方法:__aenter____aexit__。这两个方法分别用于在 async with 语句中进入和退出上下文。

  • __aenter__:当进入 async with 块时调用,它通常用于执行资源的初始化操作,并返回所管理的资源(如数据库连接、文件对象等)。

  • __aexit__:当 async with 块执行完毕后调用,通常用于执行资源的清理工作,如关闭文件、释放数据库连接等。它接收三个参数,分别是异常类型、异常值和回溯信息,用于处理异常。

示例:自定义异步上下文管理器

我们通过一个简单的例子来展示如何自定义一个异步上下文管理器。这个示例模拟了一个异步资源管理类,该类会打印打开和关闭资源的信息:

import asyncioclass AsyncResource:# 进入上下文时执行的异步操作async def __aenter__(self):print("打开资源")# 可以在此初始化资源,例如连接数据库或打开文件return self# 退出上下文时执行的异步操作async def __aexit__(self, exc_type, exc_val, exc_tb):print("关闭资源")# 可以在此释放资源,例如关闭数据库连接或文件async def main():# 使用异步上下文管理器async with AsyncResource() as resource:print("使用资源")# 在这里可以执行需要的操作,例如使用数据库连接等asyncio.run(main())
代码解释:
  1. __aenter__ 方法:该方法在进入 async with 语句时执行。我们在其中模拟了打开资源的操作。可以在这里实现需要异步处理的资源初始化,如打开异步数据库连接、打开文件流等。__aenter__ 方法的返回值将作为 async with 语句块中的变量(在此例中是 resource)。

  2. __aexit__ 方法:该方法在退出 async with 语句时执行,无论是正常退出还是因为异常退出。我们可以在这里处理资源的清理工作,例如关闭数据库连接、关闭文件流等。如果在 async with 块中发生了异常,__aexit__ 方法会接收异常类型、异常值和回溯信息,可以选择忽略异常或处理它。

  3. async with 语法:在 async with 语句块中,我们使用资源并在块结束后自动执行清理操作。async with 语法保证了资源的安全管理,无论是正常执行完毕,还是发生异常,都会确保 __aexit__ 被调用,从而安全地关闭资源。

运行示例:
打开资源
使用资源
关闭资源
进一步扩展:异步数据库连接

我们可以将这个示例扩展到更复杂的场景,比如模拟一个异步数据库连接。以下是一个更复杂的示例,展示如何使用异步上下文管理器来管理数据库连接:

import asyncioclass AsyncDatabaseConnection:async def __aenter__(self):print("建立数据库连接")# 模拟连接到数据库的异步操作await asyncio.sleep(1)  # 假设数据库连接需要1秒self.connection = "数据库连接对象"return self.connectionasync def __aexit__(self, exc_type, exc_val, exc_tb):print("关闭数据库连接")# 模拟关闭数据库连接的异步操作await asyncio.sleep(1)  # 假设关闭连接需要1秒self.connection = Noneasync def main():# 使用异步上下文管理器管理数据库连接async with AsyncDatabaseConnection() as connection:print("使用数据库连接:", connection)# 在这里执行数据库操作,如查询或插入数据asyncio.run(main())

异步迭代器

异步迭代器适合处理流式数据。比如,当处理来自网络的连续数据时,async for 可以逐个迭代处理。

import asyncioclass AsyncIterator:def __init__(self):self.counter = 0async def __anext__(self):if self.counter < 3:await asyncio.sleep(1)  # 模拟 I/O 延迟self.counter += 1return self.counterelse:raise StopAsyncIterationdef __aiter__(self):return selfasync def main():async for number in AsyncIterator():print(f"收到数据: {number}")asyncio.run(main())

异步队列

asyncio.Queue 适用于协程之间的数据共享。可以将数据从一个任务传递给另一个任务。

import asyncioasync def producer(queue):for i in range(5):await asyncio.sleep(1)await queue.put(f"数据 {i}")print(f"生产数据 {i}")async def consumer(queue):while True:data = await queue.get()print(f"消费 {data}")queue.task_done()async def main():queue = asyncio.Queue()producer_task = asyncio.create_task(producer(queue))consumer_task = asyncio.create_task(consumer(queue))await producer_taskawait queue.join()  # 等待所有任务完成consumer_task.cancel()asyncio.run(main())

7. 性能优化和最佳实践

  1. 避免阻塞调用:在异步代码中,避免直接使用像 time.sleep() 这样的阻塞函数。用 await asyncio.sleep() 代替。

  2. 资源管理:异步任务中确保资源(如网络连接)及时释放,async with 是一个好选择。

  3. 错误处理:异步任务中记得处理可能的异常,避免因错误导致整个事件循环中断。


总结

通过 asyncio,Python 异步编程可以显著提升高并发应用的性能。这种非阻塞的执行方式让我们可以更高效地处理 I/O 操作。掌握这些技巧,不仅能写出更高效的代码,也能帮助理解现代 Web 框架的工作原理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/9226.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

设计模式之建造者模式(各项装修物料组合套餐选配场景)

前言&#xff1a; 乱码七糟&#xff0c;我时常怀疑这个成语是来形容程序猿的&#xff01; 无论承接什么样的需求&#xff0c;是不是身边总有那么几个人代码写的烂&#xff0c;但是却时常有测试小姐姐过来聊天(求改bug)、有产品小伙伴送吃的(求写需求)、有业务小妹妹陪着改代码(…

ffmpeg视频滤镜:组合两个视频为立体视频- framepack

视频描述 framepack 官方网址 > FFmpeg Filters Documentation 这个滤镜会将两个视频进行组合&#xff0c;有个前提是这两个视频的帧率、分别率必须一样。比如输入的是两个852x480 视频&#xff0c;输出可能是1704*480&#xff08;左右拼接&#xff09;、852*960&#xf…

【K8S问题系列 | 8】K8S集群资源突然爆满导致 Pod 状态变为 Pending 详细解决方案

在 Kubernetes 集群中&#xff0c;当 CPU 突然爆满时&#xff0c;Pod 可能无法获得所需的资源&#xff0c;从而导致其状态变为 Pending。以下是更详细的解决方案描述&#xff0c;有效应对这一问题。 解决方案 1: 扩展集群资源 描述 当集群资源不足以支撑当前的工作负载时&…

第18篇 :深入剖析systemverilog中 randomize 失败案例启示录(一)

经过前面章节的理论学习&#xff0c;我们对systemverilog中的随机约束&#xff0c;有一定的了解&#xff0c;那么&#xff0c;今天开始&#xff0c;着重讲述一些工作中遇到的困惑。主要通过一些例子&#xff0c;层层递进&#xff0c;举一反三&#xff0c;源于实践&#xff0c;剖…

mac端mumu模拟器adb识别不了问题

1.在终端中输入&#xff1a;system_profiler SPUSBDataType,把0x05e3 (Genesys Logic, Inc.)复制 2. 1.cd ~/.android/ 2.open . 3.找到.android/adb_usb.ini文件 将以上格式的Wendor ID放入该文件 3.依次执行 * adb devices* adb kill-server* adb start-server* adb disco…

Ubuntu版本、ROS版本与Python 版本之间的关系

引言 在机器人开发中&#xff0c;ROS&#xff08;机器人操作系统&#xff09;广泛应用于科研和工业领域&#xff0c;支持多个Ubuntu和Python版本。然而&#xff0c;随着不同Ubuntu LTS版本的发布以及Python逐渐从2.x向3.x过渡&#xff0c;ROS的版本选择和兼容性要求也在不断变化…

Linux - 信号

文章目录 一、信号的定义二、查看信号三、产生信号1、指令2、系统调用3、由软件条件产生信号4、异常5、键盘输入 四、保存信号1、补充&#xff1a;信号其他相关概念2、信号保存在哪&#xff0c;怎么保存&#xff1f;3、信号集操作函数 五、捕获信号1、概念2、捕获信号的时机3、…

PMP–知识卡片--项目干系人

项目干系人主要分为两类&#xff1a;参与项目的人和受项目影响的人。按照由近及远&#xff0c;从项目经理、项目团队等逐渐扩充至供应商、客户、监管机构等。 项目往往死在被忽略的干系人手上&#xff0c;作为项目经理&#xff0c;要尽可能地识别出来所有可能影响项目以及受项目…

MATLAB - ROS 2 分析器

系列文章目录 前言 本主题介绍如何连接 ROS 2 网络&#xff0c;分析网络图中所有元素的基本信息&#xff08;如节点名称和节点之间的信息&#xff09;&#xff0c;以及可视化与 ROS 2 节点相关的参数&#xff08;如主题、服务和操作&#xff09;之间的交互。 一、连接并查看 RO…

分组校验在Spring中的应用详解

目录 前言1. 什么是分组校验2. 分组校验的基本原理3. 分组校验的实现步骤3.1 定义分组接口3.2 在校验项中指定分组3.3 校验时指定要校验的分组3.4 默认分组和分组的继承 4. 分组校验的优势和适用场景4.1 优势4.2 适用场景 5. 常见问题与解决方案5.1 校验未生效5.2 无法识别默认…

SDL打开YUV视频

文章目录 问题1&#xff1a;如何控制帧率&#xff1f;问题2&#xff1a;如何触发退出事件&#xff1f;问题3&#xff1a;如何实时调整视频窗口的大小问题4&#xff1a;YUV如何一次读取一帧的数据&#xff1f; 问题1&#xff1a;如何控制帧率&#xff1f; 单独用一个子线程给主线…

[MySQL]索引

索引介绍 索引是帮助数据库高效获取数据的数据结构。在数据之外&#xff0c;数据库系统还维护着满足特定查找算法的数据结构&#xff0c;这些数据结构以某种方式引用数据&#xff0c; 这样就可以在这些数据结构上实现高级查找算法&#xff0c;这种数据结构就是索引。 假设我们有…

window 利用Putty免密登录远程服务器

1 在本地电脑用putty-gen生成密钥 参考1 参考2 2 服务器端操作 将公钥上传至Linux服务器。 复制上述公钥到服务器端的authorized_keys文件 mkdir ~/.ssh vi ~/.ssh/authorized_keys在vi编辑器中&#xff0c;按下ShiftInsert键或者右键选择粘贴&#xff0c;即可将剪贴板中的文…

【大数据技术基础 | 实验八】HBase实验:新建HBase表

文章目录 一、实验目的二、实验要求三、实验原理四、实验环境五、实验内容和步骤&#xff08;一&#xff09;启动HBase集群&#xff08;二&#xff09;编写项目java代码&#xff08;三&#xff09;将代码导出jar包 六、实验结果七、实验心得 一、实验目的 掌握HBase数据模型(逻…

密钥管理服务 (KMS) 故障排除指南

企业客户将密钥管理服务 (KMS) 设置为部署流程的一部分&#xff0c;因为通过该服务&#xff0c;他们可以使用简单、直接的过程在其环境中激活 Windows。 通常&#xff0c;一旦设置了 KMS 主机&#xff0c;KMS 客户端就会自动连接到主机并自行激活。 然而&#xff0c;有时该流程…

CSS的配色

目录 1 十六进制2 CSS中的十六进制2.1 十六进制颜色的基本结构2.2 十六进制颜色的范围2.3 简写形式2.4 透明度 3 CSS的命名颜色4 配色4.1 色轮4.2 互补色4.3 类似色4.4 配色工具 日常在开发小程序中&#xff0c;客户总是希望你的配色是美的&#xff0c;但是美如何定义&#xff…

基于 RNN 的语言模型

基于 RNN 的语言模型 循环神经网络&#xff08;Recurrent Neural Network, RNN&#xff09;是一类网络连接中包含环路的 神经网络的总称。 给定一个序列&#xff0c;RNN 的环路用于将历史状态叠加到当前状态上。沿着时间维度&#xff0c;历史状态被循环累积&#xff0c;并作为…

【软考网工笔记】网络基础理论——物理层

文章目录 贝尔系统 T1 载波光纤 - SFP接口差分&&曼彻斯特编码网桥MAC-in-MACQ-in-QIPv6的链路本地地址CRC校验与计算E1载波编码效率对称xDSL坚持算法-CSMAUDP头部字段万兆以太网标准 IEEE 802.3ae海明码-纠错码ARP帧中的目标MAC地址快速以太区网物理层标准 100BASE-TXM…

现代Web开发:TypeScript 深入解析与最佳实践

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 现代Web开发&#xff1a;TypeScript 深入解析与最佳实践 现代Web开发&#xff1a;TypeScript 深入解析与最佳实践 现代Web开发&a…

HCIP MPLS基础

一、 实验拓扑 二、 实验需求及解法 本实验模拟BGP路由黑洞环境&#xff0c;使用MPLS LDP解决路由黑洞。 完成以下需求&#xff1a; 1.设备IP地址配置&#xff0c;请测试直连。 sysname R1 interface GigabitEthernet0/0/0ip address 12.1.1.1 255.255.255.0interface Loop…