Python协程之asyncio
asyncio 是 Python 中的异步IO库,用来编写并发协程,适用于IO阻塞且需要大量并发的场景,例如爬虫、文件读写。
asyncio 在 Python3.4 被引入,经过几个版本的迭代,特性、语法糖均有了不同程度的改进,这也使得不同版本的 Python 在 asyncio 的用法上各不相同,显得有些杂乱,以前使用的时候也是本着能用就行的原则,在写法上走了一些弯路,现在对 Python3.7+ 和 Python3.6 中 asyncio 的用法做一个梳理,以便以后能更好的使用。
协程与asyncio
协程,又称微线程,它不被操作系统内核所管理,而完全是有程序控制,协程切换花销小,因而有更高的性能。
协程可以比作子程序,不同的是,执行过程中协程可以挂起当前状态,转而执行其他协程,在适当的时候返回来接着执行,协程间的切换不需要涉及任何系统调用或任何阻塞调用,完全由协程调度器进行调度。
Python 中以 asyncio 为依赖,使用 async/await 语法糖进行协程的创建和使用,如下 async 语法创建一个协程函数:
async def work():
pass
在协程中除了普通函数的功能外最主要的作用就是:使用 await 语法等待另一个协程结束,这将挂起当前协程,直到另一个协程产生结果再继续执行:
async def work():
await asyncio.sleep(1)
print('continue')
asyncio.sleep() 是 asyncio 包内置的协程函数,这里模拟耗时的IO操作,上面这个协程执行到这一句会挂起当前协程而去执行其他协程,直到sleep结束,当有多个协程任务是,这种切换会让它们的IO操作并行处理。
注意,执行一个协程函数并不会真正的运行它,而是会返回一个协程对象,要使协程真正的运行,需要将它们加入到事件循环中运行,官方建议 asyncio 程序应当有一个主入口协程,用来管理所有其他的协程任务:
async def main():
await work()
在 Python3.7+ 中,运行这个 asyncio 程序只需要一句:asyncio.run(main())
,而在 Python3.6 中,需要手动获取事件循环并加入协程任务:
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()
事件循环就是一个循环队列,对其中的协程进行调度执行,当把一个协程加入循环,这个协程创建的其他协程都会自动加入到当前事件循环中。
其实协程对象也不是直接运行,而是被封装成一个个待执行的 Task ,大多数情况下 asyncio 会帮我们进行封装,我们也可以提前自行封装 Task 来获得对协程更多的控制权,注意,封装 Task 需要当前线程有正在运行的事件循环,否则将引 RuntimeError,这也就是官方建议使用主入口协程的原因,如果在主入口协程之外创建任务就需要先手动获取事件循环然后使用底层的方法 loop.create_task()
,任务创建后便有了状态,可以查看运行情况,查看结果,取消任务等:
async def main():
task = asyncio.create_task(work())
print(task)
await task
print(task)
#----执行结果----#
<Task pending name='Task-2' coro=<work() running at d:\tmp\code\asy.py:5>>
<Task finished name='Task-2' coro=<work() done, defined at d:\tmp\code\asy.py:5> result=None>
asyncio.create_task()
是 Python3.7 加入的高层级API,在 Python3.6,需要使用低层级API asyncio.ensure_future()
来创建 Future,Future 也是一个管理协程运行状态的对象,与 Task 没有本质上的区别。
并发协程
通常,一个含有一系列并发协程的程序写法如下(Python3.7+):
import asyncio
import time
async def work(num: int):
'''
一个工作协程,接收一个数字,将它 +1 后返回
'''
print(f'working {num} ...')
await asyncio.sleep(1) # 模拟耗时的IO操作
print(f'{num} -> {num+1} done')
return num + 1
async def main():
'''
主协程,创建一系列并发协程并运行它们
'''
# 任务队列
tasks = [work(num) for num in range(0, 5)]
# 并发执行队列中的协程并等待结果返回
results = await asyncio.gather(*tasks)
print(results)
if __name__ == "__main__":
asyncio.run(main())
并发运行多个协程任务的关键就是 asyncio.gather(*tasks)
,它接受多个协程任务并将它们加入到事件循环,所有任务都运行完成后会返回结果列表,这里我们也没有手动封装 Task,因为 gather 函数会自动封装。
并发运行还有另一个方法 asyncio.wait(tasks)
,它们的区别是:
- gather 比 wait 更加高层,gather 可以将任务分组,一般优先使用 gather:
tasks1 = [work(num) for num in range(0, 5)]
tasks2 = [work(num) for num in range(5, 10)]
group1 = asyncio.gather(*tasks1)
group2 = asyncio.gather(*tasks2)
results1, results2 = await asyncio.gather(group1, group2)
print(results1, results2)
- 在某些定制化任务需求的时候,可以使用 wait:
# Python3.8 版本后,直接向 wait() 传入协程对象已弃用,必须手动创建 Task
tasks = [asyncio.create_task(work(num)) for num in range(0, 5)]
done, pending = await asyncio.wait(tasks)
for task in tasks:
if task in done:
print(task.result())
for p in pending:
p.cancel()
Tips
- await 语句后必须是一个 可等待对象 ,可等待对象主要有三种:Python协程,Task,Future。通常情况下没有必要在应用层级的代码中创建 Future 对象。
- 在 asyncio 程序中使用同步代码虽然并不会报错,但是也失去了并发的意义,例如网络请求,如果使用仅支持同步的 requests,在发起一次请求后在收到响应结果之前不能发起其他请求,这样要并发访问多个网页时,即使使用了 asyncio,在发送一次请求后切换到其他协程还是会因为同步问题而阻塞,并不能有速度上的提升,这时候就需要其他支持异步请求库如 aiohttp。
- 关于 asyncio 的更多更详细的操作见 官方文档
Python协程之asyncio的更多相关文章
- python协程之动态添加任务
https://blog.csdn.net/qq_29349715/article/details/79730786 python协程只能运行在事件循环中,但是一旦事件循环运行,又会阻塞当前任务.所以 ...
- python协程--asyncio模块(基础并发测试)
在高并发的场景下,python提供了一个多线程的模块threading,但似乎这个模块并不近人如意,原因在于cpython本身的全局解析锁(GIL)问题,在一段时间片内实际上的执行是单线程的.同时还存 ...
- Python协程之Gevent模块
背景 进程是操作系统分配资源的最小单位,每个进程独享4G的内存地址空间,因此进程内数据是安全的,检查间的通信需要使用特定的方法.同理,正是因为进程是数据安全的,所以导致进程的切换是一个很麻烦效率不高的 ...
- 练习PYTHON协程之GREENLET
STACKLESS就算了,了解一下原理即可. GREENLET,GEVENT,EVENTLET这些,比较好测试,还是都 撸一次,得个印象. 测试代码都是网上的大路货. from greenlet im ...
- python并发编程之asyncio协程(三)
协程实现了在单线程下的并发,每个协程共享线程的几乎所有的资源,除了协程自己私有的上下文栈:协程的切换属于程序级别的切换,对于操作系统来说是无感知的,因此切换速度更快.开销更小.效率更高,在有多IO操作 ...
- python协程(yield、asyncio标准库、gevent第三方)、异步的实现
引言 同步:不同程序单元为了完成某个任务,在执行过程中需靠某种通信方式以协调一致,称这些程序单元是同步执行的. 例如购物系统中更新商品库存,需要用"行锁"作为通信信号,让不同的更新 ...
- python协程详解,gevent asyncio
python协程详解,gevent asyncio 新建模板小书匠 #协程的概念 #模块操作协程 # gevent 扩展模块 # asyncio 内置模块 # 基础的语法 1.生成器实现切换 [1] ...
- python异步编程之asyncio
python异步编程之asyncio 前言:python由于GIL(全局锁)的存在,不能发挥多核的优势,其性能一直饱受诟病.然而在IO密集型的网络编程里,异步处理比同步处理能提升成百上千倍的效率, ...
- 理解Python协程:从yield/send到yield from再到async/await
Python中的协程大概经历了如下三个阶段:1. 最初的生成器变形yield/send2. 引入@asyncio.coroutine和yield from3. 在最近的Python3.5版本中引入as ...
随机推荐
- 5.21 省选模拟赛 luogu P4207 [NOI2005]月下柠檬树 解析几何 自适应辛普森积分法
LINK:月下柠檬树 之前感觉这道题很鬼畜 实际上 也就想到辛普森积分后就很好做了. 辛普森积分法的式子不再赘述 网上多的是.值得一提的是 这道题利用辛普森积分法的话就是一个解析几何的问题 而并非计算 ...
- mysql优化:explain 和 profile
此文转自:https://blog.csdn.net/hanjungua8144/article/details/84317829 一.SQL查询语句优化基本思路和原则 优化更需要优化的Query.定 ...
- 畅购商城(五):Elasticsearch实现商品搜索
好好学习,天天向上 本文已收录至我的Github仓库DayDayUP:github.com/RobodLee/DayDayUP,欢迎Star,更多文章请前往:目录导航 畅购商城(一):环境搭建 畅购商 ...
- python数据可视化编程实战PDF高清电子书
点击获取提取码:3l5m 内容简介 <Python数据可视化编程实战>是一本使用Python实现数据可视化编程的实战指南,介绍了如何使用Python最流行的库,通过60余种方法创建美观的数 ...
- c++ explict
explicit 用于一个参数的构造函数:防止隐式转换. 什么意思呢? myClass(int x); 这是个构造函数 我们可以使用 myClass a(4); 或 myClass a=4;来调用它 ...
- OAuth2.0-1
分布式授权解决方案: 其中授权服务一般放在网关服务上,资源服务指的是,挂在网关下得各个微服务 网关授权客户端>客户端拿到token>客户端拿到token到网关验证,获取token明文> ...
- H5移动端手势密码组件
项目简介 最近参加了2017年360前端星计划,完成了一个有趣的UI组件开发大作业,借机和大家分享一下移动端开发的技术啦~~ 本项目采用原生JS和Canvas实现移动端手势密码组件,支持手势密码设置和 ...
- http接口封装mqtt协议
前言 .Net Core 3.1 WebApi 列出了mqtt客户端的封装目的是为了了解运作机制 1.封装mqtt客户端 mqtt底层协议基于MQTTnet 版本2.8.5 github地址 实例化[ ...
- wifi渗透
前言 本文主要讲述 家庭家庭家庭中(重要的事情说三遍,企业认证服务器的wifi一般非常非常的安全破解不来)如何破解wifi密码,破解wifi密码后的内网渗透利用(简单说明),如何设置wifi路由器更安 ...
- C#LeetCode刷题,走进Google,走近人生
概述 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/1015 访问. 本系列博文将会向大家展示我在LeetCode上的刷 ...