Python异步编程-协程

1、引言

在使用多个爬虫脚本进行数据爬取和调用大语言模型返回结果的场景中,涉及到大量的网络IO操作。协程能够让网络IO操作并发执行,极大地提升程序的运行效率。在智能体相关的开源项目中,我们也可以经常看到协程的身影。

2、协程

协程,英文Coroutines,是一种比线程更加轻量级的存在。正如一个进程可以拥有多个线程一样,一个线程也可以拥有多个协程。它本质上还是一段运行在单进程单线程上的函数,并不能提升你的运算速度。它比较适合处理需要等待的任务,例如网络的通讯。

特点:

  • 协作性:它允许单线程内的任务协作执行,避免传统线程中的竞争条件。
  • 切换效率:不同于系统级的线程上下文切换,协程切换在用户级实现,效率更高,开销更小
  • 非抢占式:协程依赖程序员显式地进行任务切换,而不是由操作系统判定,赋予了更大的控制自由度。

 3、asyncio异步框架

在python中,协程通过asyncio标准库能够很方便地使用

3.1、核心概念

3.1.1、协程(Coroutine)

协程是一种特殊的函数,它允许在执行过程中暂停和恢复。在Python的asyncio库中,协程是通过async关键字定义的。它和普通的函数不同,普通的函数在调用时会直接执行,而协程需要被事件循环调度执行。

特点

  • 非阻塞:协程在执行过程中,当遇到需要等待的操作(如I/O操作)时,不会像传统线程那样阻塞整个程序。它会暂停自己的执行,让出控制权,让其他协程运行,从而实现高效的并发。
  • 轻量级:创建和切换协程的开销比线程小得多。线程切换需要操作系统级别的资源调度,而协程的切换是在用户态完成的,所以可以创建大量的协程。
import asyncioasync def say_hello():print("Hello")await asyncio.sleep(1)  # 模拟异步的I/O操作,暂停协程print("World")# 创建事件循环
loop = asyncio.get_event_loop()
# 运行协程
loop.run_until_complete(say_hello())
loop.close()

注意:直接调用协程函数不会运行里面的程序返回结果,而是会返回一个协程对象。

在这个例子中,say_hello函数是一个协程。当执行到await asyncio.sleep(1)时,协程会暂停执行。asyncio.sleep是一个异步的等待函数,它模拟了I/O操作。如果在这个协程暂停期间有其他协程需要运行,事件循环就可以调度其他协程执行。

3.1.2、事件循环(Event Loop)

事件循环是asyncio程序的核心。它负责调度协程的执行,管理协程的暂停和恢复。事件循环会不断地检查协程的状态,当协程暂停时,它会记录下来,并在合适的时机(如I/O操作完成)恢复协程的执行。

功能

  • 调度协程:事件循环根据协程的执行状态(如是否遇到await暂停)来安排协程的执行顺序。
  • 处理I/O事件:它会监听各种I/O事件(如文件读写、网络通信等)。当I/O操作完成时,事件循环会唤醒等待该I/O操作的协程。
import asyncioasync def task1():print("Task 1 started")await asyncio.sleep(2)  # 模拟耗时操作print("Task 1 finished")async def task2():print("Task 2 started")await asyncio.sleep(1)  # 模拟耗时操作print("Task 2 finished")async def main():# 创建两个任务task1_obj = asyncio.create_task(task1())task2_obj = asyncio.create_task(task2())# 等待两个任务完成,await关键字用于获取任务执行的结果await task1_objawait task2_obj# 创建事件循环
loop = asyncio.get_event_loop()
# 运行主协程
loop.run_until_complete(main())
loop.close()

在这个例子中,事件循环负责调度task1task2这两个协程。task1task2都会在执行到await asyncio.sleep时暂停。事件循环会先启动task1,当task1暂停后,它会启动task2。当task2await asyncio.sleep(1)完成时,task2会恢复执行并完成。而task1会在await asyncio.sleep(2)完成时恢复执行并完成。通过这种方式,事件循环实现了两个协程的并发执行,而不是像传统线程那样需要复杂的线程切换和同步机制。

3.1.3、Future

Future是一个表示异步操作最终结果的对象。它是一个低层次的接口,用于表示异步操作的最终完成(或失败)以及其结果。主要用于底层库来封装异步操作的结果。它允许你注册回调函数,当异步操作完成时,这些回调函数会被调用。

特点:

  • Future 本身并不执行任何操作,它只是一个容器,用来存储异步操作的结果。
  • 你可以通过 Future 的方法(如 add_done_callback)来添加回调函数,当异步操作完成时,这些回调函数会被调用。
  • Future 的状态可以是 PENDING(等待中)、FINISHED(已完成或已取消)。
import asyncioasync def main():loop = asyncio.get_running_loop()future = loop.create_future()print("Future state:", future._state)  # PENDING# 设置 Future 的结果future.set_result("Hello, Future!")# 等待 Future 完成result = await futureprint("Future result:", result)  # Hello, Future!asyncio.run(main())

 3.1.4、任务(Task)

Task是Future的一个子类,它封装了一个协程(coroutine)的执行。它用于调度协程的运行,并且可以管理协程的执行状态。主要用于调度和管理协程的执行。它是asyncio中用于并发执行协程的主要方式。

特点:

  • Task 是一个高级的接口,它在内部封装了协程的执行逻辑。
  • 你可以通过 asyncio.create_task()loop.create_task() 来创建一个 Task
  • Task 会自动处理协程的运行,包括调度、执行和结果管理。
  • Task 的状态可以是 PENDING(等待中)、FINISHED(已完成或已取消)。
import asyncioasync def my_coroutine():await asyncio.sleep(1)return "Hello, Task!"async def main():task = asyncio.create_task(my_coroutine())  # 创建 Taskprint("Task state:", task._state)  # PENDING# 等待 Task 完成result = await taskprint("Task result:", result)  # Hello, Task!asyncio.run(main())

 3.1.5、Future和Task的区别

1、功能层次

  • Future 是一个低层次的接口,主要用于底层库来封装异步操作的结果。
  • Task 是一个高层次的接口,主要用于调度和管理协程的执行。

2、用途

  • Future 是一个容器,用来存储异步操作的结果,通常由底层库创建和管理。
  • Task 是用来封装和调度协程的执行,通常由用户代码创建和管理。

3、创建方式

    • Future 可以通过 loop.create_future() 创建。
    • Task 可以通过 asyncio.create_task()loop.create_task() 创建。

4、执行逻辑

  • Future 本身并不执行任何操作,它只是一个容器。
  • Task 会自动调度和执行封装的协程。

5、回调机制

  • Future 可以通过 add_done_callback 添加回调函数。
  • Task 也可以通过 add_done_callback 添加回调函数,但通常不需要手动添加,因为 Task 会自动处理协程的执行和结果。

在实际使用中,大多数情况下我们直接使用Task来调度协程的执行,而Future更多是被底层库使用。

3.2、基本用法

3.2.1、运行协程

要运行一个协程,你可以使用asyncio.run()函数。它会创建一个事件循环,并运行指定的协程。

import asyncioasync def main():print("Start")await asyncio.sleep(1)print("End")asyncio.run(main())

3.2.2、并发执行多个任务

你可以使用asyncio.gather()函数并发执行多个协程,并等待它们全部完成。

import asyncioasync def task1():print("Task 1 started")await asyncio.sleep(1)print("Task 1 finished")async def task2():print("Task 2 started")await asyncio.sleep(2)print("Task 2 finished")async def main():await asyncio.gather(task1(), task2())asyncio.run(main())

3.2.3、注意事项

1、Python版本:部分功能需Python3.7+(如asyncio.run())

2、阻塞操作:避免在协程中使用同步阻塞代码

4、代码示例

下面将使用asyncio模拟异步执行三个爬虫函数

import asyncio
import timeasync def spider1():print("Spider 1 started")await asyncio.sleep(1)print("Spider 1 finished")async def spider2():print("Spider 2 started")await asyncio.sleep(2)print("Spider 2 finished")async def spider3():print("Spider 3 started")await asyncio.sleep(3)print("Spider 3 finished")async def main():# 创建任务task1 = asyncio.create_task(spider1())task2 = asyncio.create_task(spider2())task3 = asyncio.create_task(spider3())# 等待所有任务完成await asyncio.gather(task1,task2,task3)if __name__ == '__main__':start_time = time.time()# 获取事件循环loop = asyncio.get_event_loop()# 运行主函数loop.run_until_complete(main())# 关闭事件循环loop.close()end_time = time.time()print(f"程序执行时间:{end_time-start_time}")

如果三个爬虫函数同步执行,则完成一共需要6s。通过协程异步执行,只需要3s多,极大地节省了程序的运行时间。

代码优化:使用asyncio.run()

从Python3.7开始,推荐使用asyncio.run()来运行主函数,这样可以简化事件循环的管理。

if __name__ == '__main__':start_time = time.time()asyncio.run(main())  # 使用 asyncio.run() 运行主函数end_time = time.time()print(f"程序执行时间:{end_time - start_time}")

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

大语言模型提示词(LLM Prompt)工程系统性学习指南:从理论基础到实战应用的完整体系

文章目录 前言:为什么提示词工程成为AI时代的核心技能一、提示词的本质探源:认知科学与逻辑学的理论基础1.1 认知科学视角下的提示词本质信息处理理论的深层机制图式理论的实际应用认知负荷理论的优化策略 1.2 逻辑学框架下的提示词架构形式逻辑的三段论…

Android音频开发:Speex固定帧与变长帧编解码深度解析

引言 在Android音频开发领域,Speex作为一种开源的语音编解码器,因其优秀的窄带语音压缩能力被广泛应用。在实际开发中,帧处理策略的选择直接影响着音频传输质量、带宽占用和系统资源消耗。本文将深入探讨Speex编解码中固定帧与变长帧的实现差…

Docke启动Ktransformers部署Qwen3MOE模型实战与性能测试

docker运行Ktransformers部署Qwen3MOE模型实战及 性能测试 最开始拉取ktransformers:v0.3.1-AVX512版本,发现无论如何都启动不了大模型,后来发现是cpu不支持avx512指令集。 由于本地cpu不支持amx指令集,因此下载avx2版本镜像: …

算术操作符与类型转换:从基础到精通

目录 前言:从基础到实践——探索运算符与类型转换的奥秘 算术操作符超级详解 算术操作符:、-、*、/、% 赋值操作符:和复合赋值 单⽬操作符:、--、、- 前言:从基础到实践——探索运算符与类型转换的奥秘 在先前的文…

飞腾D2000,麒麟系统V10,docker,ubuntu1804,小白入门喂饭级教程

#下载docker Index of linux/static/stable/ 根据电脑的CPU类型选择: Intel和AMD选x86_64飞腾D2000选aarch64 #选择较新的版本 #在包含下载的docker-XX.X.X.tgz的文件夹中右键->打开终端 # 解压安装包(根据实际下载的文件) tar -zxvf …

启程:为何选择PHP?

一、写在前面:小公司的生存逻辑与我的困惑 我是一名在小型软件开发公司工作的Java全栈开发者。我们这类团队的现实很直白:接不到“大单子”,日常围绕各类中小项目——企业官网、内部管理系统、定制化小程序——展开。客户预算有限、交付周期…

学习使用YOLO的predict函数使用

YOLO的 result.py #2025.1.3 """ https://docs.ultralytics.com/zh/modes/predict/#inference-arguments 对yolo 目标检测、实例分割、关键点检测结果进行说明https://docs.ultralytics.com/reference/engine/results/#ultralytics.engine.results.Masks.xy 对…

Node.js: express 使用 Open SSL

OpenSSL是一个开源的核心加密工具包,提供行业标准的加密,证书管理和安全通信功能。包含完整的 SSL/TLS 协议实现,被广泛应用于构建互联网安全基础设施。 在 express 中使用 openssl 通常是为了实现 HTTPS 通信,通过 SSL/TLS 加密来…

AI赋能的浏览器自动化:Playwright MCP安装配置与实操案例

以下是对Playwright MCP的简单介绍: Playwright MCP 是一个基于 Playwright 的 MCP 工具,提供浏览器自动化功能不要求视觉模型支持,普通的文本大语言模型就可以通过结构化数据与网页交互支持多种浏览器操作,包括截图、点击、拖动…

【Matlab】连接SQL Server 全过程

文章目录 一、下载与安装1.1 SQL Server1.2 SSMS1.3 OLE DB 驱动程序 二、数据库配置2.1 SSMS2.2 SQL Server里面设置2.3 设置防火墙2.4 设置ODBC数据源 三、matlab 链接测试 一、下载与安装 微软的,所以直接去微软官方下载即可。 1.1 SQL Server 下载最免费的Ex…

Java编程中常见的条件链与继承陷阱

格式错误的if-else条件链 典型结构与常见错误模式 在Java编程中,if-else条件链是一种常见的多条件处理模式,其标准结构如下: if (condition1) {// 处理逻辑1 } else if (condition2) {// 处理逻辑2 } else

scss(sass)中 的使用说明

在 SCSS(Sass)中,& 符号是一个父选择器引用,它代表当前嵌套规则的外层选择器。主要用途如下: 1. 连接伪类/伪元素 scss 复制 下载 .button {background: blue;&:hover { // 相当于 .button:hoverbackgrou…

C++ 信息学奥赛总复习题答案解析

第一章 答案解析 填空题 .cpp 知识点:C 源文件的命名规范 main () 知识点:C 程序的入口函数 // ,/* */ 知识点:C 注释的两种形式 int a; 知识点:变量声明的语法 cout 知识点:输出语句的关键字 判断题…

Jenkins持续集成CI,持续部署CD,Allure报告集成以及发送电子 邮件

文章目录 一、Jenkins 的简介二、Jenkins的安装三、Jenkins 文件夹的作用四、Jenkins 的应用新建 job配置 jobjenkins 集成 Allure 报告。jenkins 集成 HTML 的报告 五、Jenkins 发送电子邮件1)安装插件:Email Extension2)开启 POP3/SMTP 服务…

算术图片验证码(四则运算)+selenium

一、表达式解析 这里假设已经识别出来表达式,如何识别验证码图片里的表达式,放在下面讲。涉及到的正则表达式的解析放在本篇文章最后面。 import re # 表达式解析(支持小数的 -*/ 和中文运算符) def parse_math_expression(text)…

使用 Laravel 中的自定义存根简化工作

在开发与外部服务、API 或复杂功能交互的应用程序时,测试几乎总是很困难。简化测试的一种方法是使用存根类。以下是我通常使用它们的方法。 福利简介 存根是接口或类的伪实现,用于模拟真实服务的行为。它们允许您: 无需调用外部服务即可测试…

将 tensorflow keras 训练数据集转换为 Yolo 训练数据集

以 https://www.kaggle.com/datasets/vipoooool/new-plant-diseases-dataset 为例 1. 图像分类数据集文件结构 (例如用于 yolov11n-cls.pt 训练) import os import csv import random from PIL import Image from sklearn.model_selection import train_test_split import s…

排序算法-归并排序与快速排序

归并排序与快速排序 快速排序是利用的递归思想:选取一个基准数,把小于基准数的放左边 大于的放右边直到整个序列有序 。快排分割函数 O(lognn), 空间 :没有额外开辟新的数组但是递归树调用函数会占用栈内存 O(logn) 。 归并排序:在递归返回的…

北大开源音频编辑模型PlayDiffusion,可实现音频局部编辑,比传统 AR 模型的效率高出 50 倍!

北大开源了一个音频编辑模型PlayDiffusion,可以实现类似图片修复(inpaint)的局部编辑功能 - 只需修改音频中的特定片段,而无需重新生成整段音频。此外,它还是一个高性能的 TTS 系统,比传统 AR 模型的效率高出 50 倍。 自回归 Tra…

MyBatis————入门

1,配置相关 我们上一期详细讲了一下使用注解来实现操作数据库的方式,我们今天使用xml来实现,有同学可能有疑问,使用注解挺方便呀,为啥还要注解呀,先来说一下注解我感觉挺麻烦的,但是我们后面要…