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 中文乱码问题深入分析
Mar 13 Python
使用tensorflow实现AlexNet
Nov 20 Python
Python实现Pig Latin小游戏实例代码
Feb 02 Python
Python 数据处理库 pandas 入门教程基本操作
Apr 19 Python
详解Django 中是否使用时区的区别
Jun 14 Python
python处理两种分隔符的数据集方法
Dec 12 Python
python通过tcp发送xml报文的方法
Dec 28 Python
超简单的Python HTTP服务
Jul 22 Python
Python操作Mongodb数据库的方法小结
Sep 10 Python
Python assert关键字原理及实例解析
Dec 13 Python
python pip安装包出现:Failed building wheel for xxx错误的解决
Dec 25 Python
pytorch实现MNIST手写体识别
Feb 14 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
PHP5 的对象赋值机制介绍
2011/08/02 PHP
php中通过eval实现字符串格式的计算公式
2017/03/18 PHP
javascript 函数使用说明
2010/04/07 Javascript
Jquery实现三层遍历删除功能代码
2013/04/23 Javascript
JS兼容浏览器的导出Excel(CSV)文件的方法
2014/05/03 Javascript
AngularJS入门教程之更多模板详解
2016/08/19 Javascript
js利用for in循环获取 一个对象的所有属性以及值的实例
2017/03/30 Javascript
移动端Ionic App 资讯上下循环滚动的实现代码(跑马灯效果)
2017/08/29 Javascript
angular4 如何在全局设置路由跳转动画的方法
2017/08/30 Javascript
Vue中v-show添加表达式的问题(判断是否显示)
2018/03/26 Javascript
JavaScript DOM元素常见操作详解【添加、删除、修改等】
2018/05/09 Javascript
vue数组对象排序的实现代码
2018/06/20 Javascript
VuePress 快速踩坑小结
2019/02/14 Javascript
Bootstrap 时间日历插件bootstrap-datetimepicker配置与应用小结
2019/05/28 Javascript
微信小程序列表时间戳转换实现过程解析
2019/10/12 Javascript
关于better-scroll插件的无法滑动bug(2021通过插件解决)
2021/03/01 Javascript
python实现教务管理系统
2018/03/12 Python
基于scrapy的redis安装和配置方法
2018/06/13 Python
Python面向对象之反射/自省机制实例分析
2018/08/24 Python
Django框架中序列化和反序列化的例子
2019/08/06 Python
python 列表、字典和集合的添加和删除操作
2019/12/16 Python
pandas factorize实现将字符串特征转化为数字特征
2019/12/19 Python
python支持多线程的爬虫实例
2019/12/21 Python
使用python脚本自动生成K8S-YAML的方法示例
2020/07/12 Python
美国Rue La La闪购网站:奢侈品、中高档品牌限时折扣
2016/10/19 全球购物
俄罗斯小米家用电器、电子产品和智能家居商店:Poood.ru
2020/04/03 全球购物
用Python写一个for循环的例子
2016/07/19 面试题
Delphi工程师笔试题
2013/09/21 面试题
《童年》教学反思
2014/02/18 职场文书
总结表彰大会主持词
2014/03/26 职场文书
2015年保育员个人工作总结
2015/05/13 职场文书
吧主申请感言怎么写
2015/08/03 职场文书
2016年优秀党务工作者先进事迹材料
2016/02/29 职场文书
CSS3鼠标悬浮过渡缩放效果
2021/04/17 HTML / CSS
css display table 自适应高度、宽度问题的解决
2021/05/07 HTML / CSS
Oracle 临时表空间SQL语句的实现
2021/09/25 Oracle