Python异步IO之协程

参考自仓库https://github.com/SparksFly8/Learning_Python/tree/master/coroutine

协程(coroutine)在多任务协作中体现的效率又极为的突出。Python中执行多任务还可以通过多进程一个进程中的多线程来执行,但两者之中均存在一些缺点,因此引出了协程。

首先需要了解同步和异步的概念:

  • 同步:发出一个“调用”时,在没有得到结果之前,该“调用”就不返回,“调用者”需要一直等待该“调用”结束才能进行下一步工作。
  • 异步:“调用”在发出之后,就直接返回了,也就没有返回结果。“被调用者”完成任务后,通过状态来通知“调用者”继续回来处理该“调用”。

普通同步代码实现多个IO任务的示例:

# 普通同步代码实现多个IO任务
import time
def taskIO_1():print('开始运行IO任务1...')time.sleep(2)  # 假设该任务耗时2sprint('IO任务1已完成,耗时2s')
def taskIO_2():print('开始运行IO任务2...')time.sleep(3)  # 假设该任务耗时3sprint('IO任务2已完成,耗时3s')start = time.time()
taskIO_1()
taskIO_2()
print('所有IO任务总耗时%.5f秒' % float(time.time()-start))
开始运行IO任务1...
IO任务1已完成,耗时2s
开始运行IO任务2...
IO任务2已完成,耗时3s
所有IO任务总耗时5.00112

在计算机中CPU的运算速率要远远大于IO速率,而当CPU运算完毕后,如果再要闲置很长时间去等待IO任务完成才能进行下一个任务的计算,这样的任务执行效率很低。

所以需要有一种异步的方式来处理类似任务

一、使用yield from和@asyncio.coroutine实现协程

yield 和 yield from

  1. yield在生成器中有中断的功能,可以传出值,也可以从函数外部接收值,而yield from的实现就是简化了yield操作,如下:
def generator_1(titles):yield titles
def generator_2(titles):yield from titlestitles = ['Python','Java','C++']
for title in generator_1(titles):print('生成器1:',title)
for title in generator_2(titles):print('生成器2:',title)

执行结果如下:

生成器1: ['Python', 'Java', 'C++']
生成器2: Python
生成器2: Java
生成器2: C++

可以看出,yield titles返回titles完整列表,而yield from titles实际等价于:

for title in titles: # 等价于yield from titlesyield title 
  1. yield from还有一个主要的功能是省去了很多异常的处理,不再需要我们手动编写,其内部已经实现大部分异常处理。
    下面是用生成器来实现一个整数加和的程序,通过send()函数向生成器中传入要加和的数字,然后最后以返回None结束,total保存最后加和的总数。

如果使用生成器g1完成这个任务,在最后传入None后的程序退出会报StopIteration异常并返回total值,如:

23None
StopIteration: 5

但是如果用g2完成这个任务,即使传入None也不报异常,total = tield from generator_1()返回给total的值是generator_1()最终的return total。这说明yield from封装了处理常见异常的代码

  1. 概念:
  • 子生成器:yield from后的generator_1()生成器函数是子生成器
  • 委托生成器:generator_2()是程序中的委托生成器,它负责委托子生成器完成具体任务。
  • 调用方:main()是程序中的调用方,负责调用委托生成器。

yield from在其中的关键作用是:建立调用方和子生成器的通道

  • 在上述代码中main()每一次在调用send(value)时,value不是传递给了委托生成器generator_2(),而是借助yield from传递给了子生成器generator_1()中的yield
  • 同理,子生成器中的数据也是通过yield直接发送到调用方main()中。

yield from结合@asyncio.coroutine实现协程(已移除)

在Python3.5及之后的版本中,asyncio.coroutine装饰器被弃用和移除了,取而代之的是async def来定义协程,这里仅做了解

# 使用同步方式编写异步功能
import time
import asyncio
@asyncio.coroutine # 标志协程的装饰器
def taskIO_1():print('开始运行IO任务1...')yield from asyncio.sleep(2)  # 假设该任务耗时2sprint('IO任务1已完成,耗时2s')return taskIO_1.__name__
@asyncio.coroutine # 标志协程的装饰器
def taskIO_2():print('开始运行IO任务2...')yield from asyncio.sleep(3)  # 假设该任务耗时3sprint('IO任务2已完成,耗时3s')return taskIO_2.__name__
@asyncio.coroutine # 标志协程的装饰器
def main(): # 调用方tasks = [taskIO_1(), taskIO_2()]  # 把所有任务添加到task中done,pending = yield from asyncio.wait(tasks) # 子生成器for r in done: # done和pending都是一个任务,所以返回结果需要逐个调用result()print('协程无序返回值:'+r.result())if __name__ == '__main__':start = time.time()loop = asyncio.get_event_loop() # 创建一个事件循环对象looptry:loop.run_until_complete(main()) # 完成事件循环,直到最后一个任务结束finally:loop.close() # 结束事件循环print('所有IO任务总耗时%.5f秒' % float(time.time()-start))
开始运行IO任务1...
开始运行IO任务2...
IO任务1已完成,耗时2s
IO任务2已完成,耗时3s
协程无序返回值:taskIO_2
协程无序返回值:taskIO_1
所有IO任务总耗时3.00209

二、使用asyncawait实现协程

在Python3.5开始引入新的语法asyncawait,以简化并更好地标识异步IO

import time
import asyncioasync def taskIO_1():print('开始运行IO任务1...')await asyncio.sleep(2)  # 假设该任务耗时2sprint('IO任务1已完成,耗时2s')return taskIO_1.__name__async def taskIO_2():print('开始运行IO任务2...')await asyncio.sleep(3)  # 假设该任务耗时3sprint('IO任务2已完成,耗时3s')return taskIO_2.__name__async def main(): # 调用方# 将协程对象转换为 Task 对象tasks = [asyncio.create_task(taskIO_1()), asyncio.create_task(taskIO_2())]# 使用 asyncio.gather 代替 asyncio.wait,以简化代码results = await asyncio.gather(*tasks)  # 等待所有任务完成并获取返回值for result in results:print('协程返回值:' + result)if __name__ == '__main__':start = time.time()# 对于 Python 3.7 及以上版本,推荐使用 asyncio.run 来运行主函数asyncio.run(main())# 由于 asyncio.run 已经处理了事件循环的创建和关闭,以下代码可以删除# loop = asyncio.get_event_loop()# try:#     loop.run_until_complete(main())# finally:#     loop.close()print('所有IO任务总耗时%.5f秒' % (time.time() - start))
开始运行IO任务1...
开始运行IO任务2...
IO任务1已完成,耗时2s
IO任务2已完成,耗时3s
协程返回值:taskIO_1
协程返回值:taskIO_2
所有IO任务总耗时3.01749

协程是函数级别的程序,多进程和多线程是内核级别的程序。

三、使用asyncio的不同方法实现协程

在多个协程中的线性控制流很容易通过内置的关键词await来管理。使用asyncio模块中的方法可以实现更多复杂的结构,它可以并发地完成多个协程。

asyncio.wait()

你可以将一个操作分成多个部分并分开执行,而wait(tasks)可以被用于中断任务集合(tasks)中的某个被事件循环轮询到的任务,直到该协程的其他后台操作完成才被唤醒

done, pending = await asyncio.wait(aws)

此处并发运行传入的aws(awaitable objects),同时通过await返回一个包含(done, pending)的元组,done表示已完成的任务列表,pending表示未完成的任务列表。

注:

  • 只有当给wait()传入timeout参数时才有可能产生pending列表。
  • 通过wait()返回的结果集是按照事件循环中的任务完成顺序排列的,所以其往往和原始任务顺序不同。
async def main(): # 调用方tasks = [taskIO_1(), taskIO_2()]  # 把所有任务添加到task中done,pending = await asyncio.wait(tasks) # 子生成器for r in done: # done和pending都是一个任务,所以返回结果需要逐个调用result()print('协程无序返回值:'+r.result())

asyncio.gather()

如果你只关心协程并发运行后的结果集合,可以使用gather(),它不仅通过await返回仅一个结果集,而且这个结果集的结果顺序是传入任务的原始顺序。

gather()通过await直接返回一个结果集列表,最后返回的结果集的顺序是按照初始传入的任务顺序排的。

async def main(): # 调用方resualts = await asyncio.gather(taskIO_1(), taskIO_2()) # 子生成器print(resualts)

asyncio.as_completed()

as_completed(tasks)是一个生成器,它管理着一个协程列表(此处是传入的tasks)的运行。当任务集合中的某个任务率先执行完毕时,会率先通过await关键字返回该任务结果。可见其返回结果的顺序和wait()一样,均是按照完成任务顺序排列的。

使用as_completed(tasks)wait(tasks)相同之处是返回结果的顺序是协程的完成顺序,这与gather()恰好相反。而不同之处是as_completed(tasks)可以实时返回当前完成的结果,而wait(tasks)需要等待所有协程结束后返回的done去获得结果。

async def main(): # 调用方tasks = [taskIO_1(), taskIO_2()]  # 把所有任务添加到task中for completed_task in asyncio.as_completed(tasks):resualt = await completed_task # 子生成器print('协程无序返回值:'+resualt)

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

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

相关文章

【机器学习】机器学习与图像识别的融合应用与性能优化新探索

文章目录 引言第一章:机器学习在图像识别中的应用1.1 数据预处理1.1.1 数据清洗1.1.2 数据归一化1.1.3 数据增强 1.2 模型选择1.2.1 卷积神经网络1.2.2 迁移学习1.2.3 混合模型 1.3 模型训练1.3.1 梯度下降1.3.2 随机梯度下降1.3.3 Adam优化器 1.4 模型评估与性能优…

【软件测试】Python自动化测试框架:unittest测试用例编写及执行

🍅 视频学习:文末有免费的配套视频可观看 🍅 点击文末小卡片,免费获取软件测试全套资料,资料在手,涨薪更快 本文将介绍 unittest 自动化测试用例编写及执行的相关内容,包括测试用例编写、测试用…

Element中的表格组件Table和分页组件Pagination

简述:在 Element UI 中,Table组件是一个功能强大的数据展示工具,用于呈现结构化的数据列表。它提供了丰富的特性,使得数据展示不仅美观而且高效。而Pagination组件是一个用于实现数据分页显示的强大工具。它允许用户在大量数据中导…

科普文:linux服务器性能调优之内核参数

https://zhouxx.blog.csdn.net/article/details/140168148 在写上面这篇“科普文:Linux服务器性能调优概叙”文章时,由于篇幅原因,将部分内核参数独立出来。 内核参数文件位置:/etc/sysctrl.conf 内核修改和生效命令:s…

【项目日记(四)】搜索引擎-Web模块

❣博主主页: 33的博客❣ ▶️文章专栏分类:项目日记◀️ 🚚我的代码仓库: 33的代码仓库🚚 🫵🫵🫵关注我带你了解更多项目内容 目录 1.前言2.前端模块2.1页面设计2.2后端交互 3.部署到云服务器4.总结 1.前言 在前面的文…

【Symfony异步任务处理】掌握队列系统的高效之道

标题:【Symfony异步任务处理】掌握队列系统的高效之道 在现代Web应用开发中,处理耗时的任务(如发送邮件、处理文件上传等)时,队列系统是一种非常有效的方式。Symfony作为一个功能丰富的PHP框架,提供了强大…

c语言--字符串处理之分割strtok

strtok() char *strtok(char s[], const char *delim); 参数: s[]是原字符串,delim为分隔符 返回:字符串拆分后的首地址 第一次拆分,参1 传待拆分的原串。 第1 次拆分时,参1传 NULL. strtok案例解析: #i…

什么是浪涌电压_浪涌电压的种类及保护器件的选型

一、浪涌电压的定义 浪涌电压,也称为浪涌电流,是指电路在遭雷击或在接通、断开电感负载或大型负载时产生的瞬时过电压或过电流。这种瞬变干扰通常发生在极短的时间内,如几百万分之一秒,但峰值电压可能极高,可能达到数…

nginx.conf配置参数解析

nginx配置文件解析 /usr/local/nginx/conf vim /etc/security/limits.conf #配置生效只能重新启动* soft nproc 65535 #能打开的进程最大数是软限制655335,65535是最大值 * hard nproc 65535 * soft nofile 65535 # 进程打开文件数的最大值65535 * hard nof…

ExtendSim在商业和服务行业中的仿真

仿真使企业能够做出明智的、数据驱动的预测,从而指导决策、产生积极成果并建立竞争优势。 精益分析 使用 ExtendSim 中的精益分析方法对欧洲的供应链网络进行建模,一家制造商实现了对最终客户的服务水平提高了 98%,而且现在可以在库存减少约 …

python 实现docx指定语言翻译(不丢失格式)

我这边有个需求需要把一份docx翻译成指定语言的文档并且保存,研究了下,记录。 首先先安装依赖 pip install python-docx1.1.2 googletrans4.0.0rc1 python-docx是用来读取docx的,googletrans使用来翻译的。 googletrans PyPI 这个是官方文…

用Vue3和Rough.js绘制一个交互式3D图

本文由ScriptEcho平台提供技术支持 项目地址:传送门 基于Rough.js和GSAP创建交互式SVG图形卡片 应用场景 本代码适用于需要创建动态交互式SVG图形卡片的场景,例如网页设计、数据可视化和交互式艺术作品。 基本功能 该代码利用Rough.js和GSAP库&…

流批一体计算引擎-13-[Flink]RuntimeExecutionMode和水印策略Watermark Strategy

1 运行时执行模式RuntimeExecutionMode 数据流程序的运行时执行模式。除此之外,它还控制任务 调度、网络洗牌行为和时间语义。一些业务也将发生变化 它们根据配置的执行模式记录发射行为。 (1)RuntimeExecutionMode.STREAMING,管道将使用流语义执行。开始执行之前将部署所有…

Android中android.fg线程和android.ui线程分别代表什么?

Android中android.fg线程和android.ui线程分别代表什么? android.fg线程(FgThread): FgThread是Android系统中一个特殊的线程,其类定义大致为public final class FgThread extends ServiceThread。它主要用于提供一个…

gitignore

.gitignore 是一个在 Git 版本控制系统中使用的文件,它用于指定在 Git 仓库中哪些文件或目录应该被忽略,即不会被 Git 追踪或记录。这对于一些自动生成的、用户特定的或敏感的文件特别有用,比如编译后的目标文件、日志文件、配置文件中的密码…

前端学习(五)CSS浮动与补白

目录&#xff1a; 内容&#xff1a; //设置左右浮动 .left{float:left; } .right{float:right; } /*必须设置不同浮动*/ //创建div <div> <dic class"left">左边</div> <div class"right">右边</div> </div> //设置浮…

Spring 异常捕获后依旧回滚

问题抛出 在 Spring 的声明式事务中手动捕获异常&#xff0c;居然判定回滚了&#xff0c;例如 A 里面调用 B &#xff0c;C &#xff0c;C 里面抛出了异常&#xff0c;A 里面对 C 进行了 try catch 依然会回滚&#xff0c;上代码 EnableTransactionManagement SpringBootAppl…

地铁车厢火灾3D模拟逃生演习减少了资源损耗和风险

在消防安全领域&#xff0c;为了更好地提升安全实训效果&#xff0c;我们在VR安全培训领域打造了多款消防安全VR模拟实训系统&#xff0c;不仅实现了与现实世界无异的交互操作&#xff0c;更在虚拟空间中超越了现实的限制&#xff0c;模拟出那些现实中难以搭建的复杂场景。 利用…

mysql:部署MySQL 8.0 环境

mysql网址&#xff1a;MySQL 点击 MySQL Community Server 选择合适的版本 选择8.0版本 下载完成&#xff0c;点击mysql-installer-community-8.0.26.0.msi文件&#xff0c;打开安装向导。 选择自定义安装类型 打开“Select Products” 窗口&#xff0c;可以定制需要安装的产…

成都晨持绪:开一家抖音网店到底能不能赚钱

在数字化时代的浪潮中&#xff0c;抖音以其独特的魅力迅速占领了社交媒体的舞台。众多创业者纷纷把目光投向这个新兴平台&#xff0c;企图在短视频的海洋里找到属于自己的财富岛屿。但是&#xff0c;开一家抖音网店到底能不能赚钱呢? 我们要认识到&#xff0c;抖音作为一个流量…