python - asyncio异步编程


Posted in Python onApril 06, 2021

1.   想学asyncio,得先了解协程

携程的意义:

  1. 计算型的操作,利用协程来回切换执行,没有任何意义,来回切换并保存状态 反倒会降低性能。
  2. IO型的操作,利用协程在IO等待时间就去切换执行其他任务,当IO操作结束后再自动回调,那么就会大大节省资源并提供性能,从而实现异步编程(不等待任务结束就可以去执行其他代码

2.协程和多线程之间的共同点和区别:

共同点:

都是并发操作,多线程同一时间点只能有一个线程在执行,协程同一时间点只能有一个任务在执行;

不同点:

多线程,是在I/O阻塞时通过切换线程来达到并发的效果,在什么情况下做线程切换是由操作系统来决定的,开发者不用操心,但会造成竞争条件 (race condition) ;

协程,只有一个线程,在I/O阻塞时通过在线程内切换任务来达到并发的效果,在什么情况下做任务切换是开发者决定的,不会有竞争条件 (race condition) 的情况;多线程的线程切换比协程的任务切换开销更大;
对于开发者而言,多线程并发的代码比协程并发的更容易书写。

一般情况下协程并发的处理效率比多线程并发更高。

3. greenlet实现协程

greenlet用于创建协程,switch用于进行协程之间的切换某个协程在执行的过程中可以随时的被其他协程通过switch函数来打断,转而去执行其他协程,当前协程的中断现场会被保留,一旦中断的协程再次获得cpu的执行权首先会恢复现场然后从中断处继续执行这种机制下的协程是同步,不能并发

pip install greenlet

import time
import greenlet
 
 
def func1():
  print("func11")
  gr2.switch()
  time.sleep(1)
  print("func22")
  gr2.switch()
 
 
def func2():
  print("func33")
  gr1.switch()
  time.sleep(1)
  print("func44")
 
 
start = time.time()
gr1 = greenlet.greenlet(func1)
gr2 = greenlet.greenlet(func2)
gr1.switch()
end = time.time()
print(end - start)

4. yield关键字实现协程

def func1():
  yield 1
  yield from func2()
  yield 3
 
 
def func2():
  yield 2
  yield 4
 
 
ff = func1()
for item in ff:
  print(item)

5.gevent协程

(1)gevent实现协程

pip install gevent

from greenlet import greenlet
from time import sleep
def func1():
  print("协程1")
  sleep(2)
  g2.switch()
  print("协程1恢复运行")
 
def func2():
  print("协程2")
  sleep(1)
  g3.switch()
def func3():
  print("协程3")
  sleep(1)
  g1.switch()
 
if __name__ == '__main__':
  # 使用greenlet来创建三个协程
  g1 = greenlet(func1)
  g2 = greenlet(func2)
  g3 = greenlet(func3)
  # print(g1)
  g1.switch() # 让协程g1取抢占cpu资源

(2) gevent实现异步协程

# 协程被创建出来以后默认是多个协程同步执行
# 我们可以加入monkey补丁,把同步的协程转成异步协程
from gevent import monkey # 注意:monkey的引入必须在其他模块之前
 
monkey.patch_all() # 用monkey给整个协程队列,添加一个非阻塞I/O的补丁,使得他们成为异步协程
import time
import requests
import gevent
 
headers = {
  'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'}
 
 
def func(url, i):
  print("协程%d开启!" % i)
  res = requests.get(url=url, headers=headers)
  html = res.text
  print("协程%d执行结束,获取到的响应体大小为:%d" % (i, len(html)))
 
 
if __name__ == '__main__':
  start = time.time()
  urls = [
    "https://www.baidu.com/",
    "https://www.qq.com/",
    "https://www.sina.com.cn",
    "https://www.ifeng.com/",
    "https://www.163.com/"
  ]
  # 创建5个协程分别对上面5个网站进行访问
  g_list = []
  for i in range(len(urls)):
    g = gevent.spawn(func, urls[i], i)
    g_list.append(g)
    # func(urls[i], i)
  gevent.joinall(g_list)
  end = time.time()
  print(end - start)

6. asyncio模块实现异步协程

在python3.4及之后的版本使用,asyncio厉害之处在于:遇到IO操作时会自动切换执行其它任务

import time
import asyncio
 
 
@asyncio.coroutine
def func1():
  print(1)
  yield from asyncio.sleep(1) # 遇到IO耗时操作,自动切换到tasks中的其它任务
  print(2)
 
 
@asyncio.coroutine
def func2():
  print(3)
  yield from asyncio.sleep(1) # 遇到IO耗时操作,自动切换到tasks中的其它任务
  print(4)
 
 
tasks = [
  asyncio.ensure_future(func1()),
  asyncio.ensure_future(func2())
]
 
start = time.time()
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
end = time.time()
print(end - start)

7. asyc & await关键字实现异步编程(现在推荐使用的用法)

在python3.5及之后的版本中可以使用

import time
import asyncio
 
 
async def func1():
  print(1)
  await asyncio.sleep(1)
  print(2)
 
 
async def func2():
  print(3)
  await asyncio.sleep(1)
  print(4)
 
 
tasks = [
  asyncio.ensure_future(func1()),
  asyncio.ensure_future(func2())
]
 
start = time.time()
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
end = time.time()
print(end - start)

7.1 事件循环

    事件循环,可以把他当做是一个while循环,这个while循环在周期性的运行并执行一些任务,在特定条件下终止循环。

伪代码:

# 伪代码
任务列表 = [ 任务1, 任务2, 任务3,... ]
while True:
  可执行的任务列表,已完成的任务列表 = 去任务列表中检查所有的任务,将'可执行'和'已完成'的任务返回
  for 就绪任务 in 已准备就绪的任务列表:
    执行已就绪的任务
  for 已完成的任务 in 已完成的任务列表:
    在任务列表中移除 已完成的任务
  如果 任务列表 中的任务都已完成,则终止循环

7.2 协程和异步编程

协程函数,定义形式为 async def 的函数。

协程对象,调用 协程函数 所返回的对象。

# 定义一个协程函数
async def func():
  pass
# 调用协程函数,返回一个协程对象
result = func()

注意:调用协程函数时,函数内部代码不会执行,只是会返回一个协程对象。 

7.3 基本应用

程序中,如果想要执行协程函数的内部代码,需要 事件循环 和 协程对象 配合才能实现,如:

import asyncio
async def func():
  print("协程内部代码")
# 调用协程函数,返回一个协程对象。
result = func()
# 方式一
# loop = asyncio.get_event_loop() # 创建一个事件循环
# loop.run_until_complete(result) # 将协程当做任务提交到事件循环的任务列表中,协程执行完成之后终止。
# 方式二
# 本质上方式一是一样的,内部先 创建事件循环 然后执行 run_until_complete,一个简便的写法。
# asyncio.run 函数在 Python 3.7 中加入 asyncio 模块,
asyncio.run(result)

这个过程可以简单理解为:将协程当做任务添加到 事件循环 的任务列表,然后事件循环检测列表中的协程是否 已准备就绪(默认可理解为就绪状态),如果准备就绪则执行其内部代码。

7.4 await关键字

await是一个只能在协程函数中使用的关键字,用于遇到IO操作时挂起 当前协程(任务),当前协程(任务)挂起过程中 事件循环可以去执行其他的协程(任务),当前协程IO处理完成时,可以再次切换回来执行await之后的代码,

await + 可等待对象(协程对象、Future对象、Task对象)

示例1:await+协程对象

import asyncio
 
 
async def func1():
  print("start")
  await asyncio.sleep(1)
  print("end")
  return "func1执行完毕"
 
 
async def func2():
  print("func2开始执行")
  # await关键字后面可以跟可等待对象(协程对象、Future对象、Task对象)
  response = await func1()
  print(response)
  print("func2执行完毕")
 
 
asyncio.run(func2())

示例2: 协程函数中可以使用多次await关键字

import asyncio
 
 
async def func1():
  print("start")
  await asyncio.sleep(1)
  print("end")
  return "func1执行完毕"
 
 
async def func2():
  print("func2开始执行")
  # await关键字后面可以跟可等待对象(协程对象、Future对象、Task对象)
  response = await func1()
  print(response)
  response2 = await func1()
  print(response2)
  print("func2执行完毕")
 
 
asyncio.run(func2())

7.5 task对象

Tasks用于并发调度协程,通过asyncio.create_task(协程对象)的方式创建Task对象,这样可以让协程加入事件循环中等待被调度执行。除了使用 asyncio.create_task() 函数以外,还可以用低层级的 loop.create_task() 或 ensure_future() 函数。不建议手动实例化 Task 对象。

本质上是将协程对象封装成task对象,并将协程立即加入事件循环,同时追踪协程的状态。

注意:asyncio.create_task() 函数在 Python 3.7 中被加入。在 Python 3.7 之前,可以改用低层级的 asyncio.ensure_future() 函数。

示例1:

import asyncio
 
 
async def func():
  print(1)
  await asyncio.sleep(1)
  print(2)
  return "func的返回值"
 
 
async def main():
  print(3)
  # 创建协程,将协程封装到一个task对象中并立即添加到事件循环列表中,等待事件循环去执行,(默认是就绪状态)
  task1 = asyncio.create_task(func())
  # 创建协程,将协程封装到一个task对象中并立即添加到事件循环列表中,等待事件循环去执行,(默认是就绪状态)
  task2 = asyncio.create_task(func())
  # 当执行某协程遇到IO操作时,会自动化切换执行其他任务。
  # 此处的await是等待相对应的协程全都执行完毕并获取结果
  ret1 = await task1
  ret2 = await task2
  print(ret1, ret2)
 
 
asyncio.run(main())

示例2:用的还是比较多的

import asyncio
 
 
async def func():
  print(1)
  await asyncio.sleep(1)
  print(2)
  return "func的返回值"
 
 
async def main():
  print(3)
  # 创建协程,将协程封装到Task对象中并添加到事件循环的任务列表中,等待事件循环去执行(默认是就绪状态)。
  # 在调用
  task_list = [
    asyncio.create_task(func()),
    asyncio.create_task(func())
  ]
  # 当执行某协程遇到IO操作时,会自动化切换执行其他任务。
  # 此处的await是等待所有协程执行完毕,并将所有协程的返回值保存到done
  # 如果设置了timeout值,则意味着此处最多等待的秒,完成的协程返回值写入到done中,未完成则写到pending中。
  done, pending = await asyncio.wait(task_list, timeout=None)
  print(done)
  print(pending)
 
 
asyncio.run(main())

 示例3:

import asyncio
 
 
async def func():
  print("执行协程函数内部代码")
  # 遇到IO操作挂起当前协程(任务),等IO操作完成之后再继续往下执行。当前协程挂起时,事件循环可以去执行其他协程(任务)。
  response = await asyncio.sleep(2)
  print("IO请求结束,结果为:", response)
 
 
coroutine_list = [func(), func()]
# 错误:coroutine_list = [ asyncio.create_task(func()), asyncio.create_task(func()) ]
# 此处不能直接 asyncio.create_task,因为将Task立即加入到事件循环的任务列表,
# 但此时事件循环还未创建,所以会报错。
# 使用asyncio.wait将列表封装为一个协程,并调用asyncio.run实现执行两个协程
# asyncio.wait内部会对列表中的每个协程执行ensure_future,封装为Task对象。
done, pending = asyncio.run(asyncio.wait(coroutine_list))

总结:

在程序中只要看到asyncawait关键字,其内部就是基于协程实现的异步编程,这种异步编程是通过一个线程在IO等待时间去执行其他任务,从而实现并发。

如果是 I/O 密集型,且 I/O 请求比较耗时的话,使用协程。
如果是 I/O 密集型,且 I/O 请求比较快的话,使用多线程。
如果是 计算 密集型,考虑可以使用多核 CPU,使用多进程。

以上就是python中asyncio异步编程学习的详细内容,更多关于python中使用asyncio的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
Python正则表达式介绍
Aug 06 Python
使用Python编写简单网络爬虫抓取视频下载资源
Nov 04 Python
跟老齐学Python之使用Python操作数据库(1)
Nov 25 Python
将Python的Django框架与认证系统整合的方法
Jul 24 Python
python实现微信跳一跳辅助工具步骤详解
Jan 04 Python
Python判断一个文件夹内哪些文件是图片的实例
Dec 07 Python
详解Django定时任务模块设计与实践
Jul 24 Python
Django 实现 Websocket 广播、点对点发送消息的代码
Jun 03 Python
解决Keras TensorFlow 混编中 trainable=False设置无效问题
Jun 28 Python
Python爬虫基于lxml解决数据编码乱码问题
Jul 31 Python
无惧面试,带你搞懂python 装饰器
Aug 17 Python
python爬取代理ip的示例
Dec 18 Python
python - timeit 时间模块
Apr 06 #Python
python制作图形界面的2048游戏, 基于tkinter
python第三方网页解析器 lxml 扩展库与 xpath 的使用方法
Apr 06 #Python
python删除csv文件的行列
Apr 06 #Python
python使用pygame创建精灵Sprite
python 逐步回归算法
python 通过使用Yolact训练数据集
You might like
PHP学习之PHP运算符
2006/10/09 PHP
php is_file 判断给定文件名是否为一个正常的文件
2010/05/10 PHP
PHP 事件机制(2)
2011/03/23 PHP
php学习之数据类型之间的转换介绍
2011/06/09 PHP
PHP精确计算功能示例
2016/11/29 PHP
PHP实现下载远程图片保存到本地的方法
2017/06/19 PHP
Javascript注入技巧
2007/06/22 Javascript
图片连续滚动代码[兼容IE/firefox]
2009/06/11 Javascript
ajax 同步请求和异步请求的差异分析
2011/07/04 Javascript
从数据结构分析看:用for each...in 比 for...in 要快些
2013/04/17 Javascript
使用javascript实现json数据以csv格式下载
2015/01/09 Javascript
jQuery使用模式窗口实现在主页面和子页面中互相传值的方法
2016/03/01 Javascript
jQuery和hwSlider实现内容响应式可触控滑动切换效果附源码下载(二)
2016/06/22 Javascript
使用Bootstrap typeahead插件实现搜索框自动补全的方法
2016/07/07 Javascript
AngularJS 指令详细介绍
2016/07/27 Javascript
利用js定义一个导航条菜单
2017/03/14 Javascript
Vue-Cli中自定义过滤器的实现代码
2017/08/12 Javascript
10个最优秀的Node.js MVC框架
2017/08/24 Javascript
vue elementUI tree树形控件获取父节点ID的实例
2018/09/12 Javascript
JavaScript在web自动化测试中的作用示例详解
2019/08/25 Javascript
微信小程序 SOTER 生物认证DEMO 指纹识别功能
2019/12/13 Javascript
[01:32]2016国际邀请赛中国区预选赛IG战队首日赛后采访
2016/06/27 DOTA
python教程之用py2exe将PY文件转成EXE文件
2014/06/12 Python
python中argparse模块用法实例详解
2015/06/03 Python
浅谈Python类里的__init__方法函数,Python类的构造函数
2016/12/10 Python
Python基于回溯法子集树模板解决野人与传教士问题示例
2017/09/11 Python
pandas分区间,算频率的实例
2019/07/04 Python
python requests抓取one推送文字和图片代码实例
2019/11/04 Python
Python使用urllib模块对URL网址中的中文编码与解码实例详解
2020/02/18 Python
Opencv 图片的OCR识别的实战示例
2021/03/02 Python
Java面向对象面试题
2016/12/26 面试题
学生会部长竞聘书
2014/03/31 职场文书
无偿献血倡议书
2014/04/14 职场文书
Nginx的反向代理实例详解
2021/03/31 Servers
Python Django搭建文件下载服务器的实现
2021/05/10 Python
JavaScript offset实现鼠标坐标获取和窗口内模块拖动
2021/05/30 Javascript