python中asyncio异步编程学习
作者:专职 发布时间:2022-10-20 06:19:37
1. 想学asyncio,得先了解协程
携程的意义:
计算型的操作,利用协程来回切换执行,没有任何意义,来回切换并保存状态 反倒会降低性能。
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))
总结:
在程序中只要看到async
和await
关键字,其内部就是基于协程实现的异步编程,这种异步编程是通过一个线程在IO等待时间去执行其他任务,从而实现并发。
如果是 I/O 密集型,且 I/O 请求比较耗时的话,使用协程。
如果是 I/O 密集型,且 I/O 请求比较快的话,使用多线程。
如果是 计算 密集型,考虑可以使用多核 CPU,使用多进程。
来源:https://blog.csdn.net/weixin_42289273/article/details/115440157
猜你喜欢
- 在找工作的时候,我们会选择上网查询招聘的信息,或者是通过一些招聘会进行现场面试。但由于信息更新不及时,有一些岗位会出现下架的情况,如果我们不
- 使用Keras如果要使用大规模数据集对网络进行训练,就没办法先加载进内存再从内存直接传到显存了,除了使用Sequence类以外,还可以使用迭
- 说起页面执行时间相信大家并不陌生,如果你经常逛论坛,就会发现很多论坛底部都显示有页面执行时间,那么什么是页面执行时间呢?我个人理解是一个网页
- 这篇文章主要介绍了python如果快速判断数字奇数偶数,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋
- 因为我们现在的前端框架做性能优化,为了找到各个组件及框架的具体解析耗时,需要在框架中嵌入一个耗时测试工具,性能测试跟不同的计算机硬件配置有很
- 单分支结构:if 语句Python 中 if 语句的语法格式如下:if <条件>:    
- SQL Server定位于中型的数据库应用,操作较Oracle和MySQL等要相对简便,SQL Server在处理海量数据的效率,后台开发的
- 在使用Python多年以后,我偶然发现了一些我们过去不知道的功能和特性。一些可以说是非常有用,但却没有充分利用。考虑到这一点,我编辑了一些你
- httplib 是 python中http 协议的客户端实现,可以使用该模块来与 HTTP 服务器进行交互。httplib的内容不是很多,也
- 列表的添加1)+ 添加2)append 追加一次只能添加一个元素到列表中,适合用于循环里3)extend 拉伸可一次添加多个元素到列表中4)
- 一, 当新增节点后刷新当前节点node.loaded = false;node.expand(); //新建子节点是刷新一次本节点的展开请求
- 前言typescript作为未来前端开发的主流框架,在前端开发的过程中也会越来越主要,相信这篇文章会对你有很大的帮助!开发环境搭建创建min
- 方法一:1、进入项目目录下,运行下面程序:nohup python manage.py runserver 0.0.0.0:5008 &am
- identity-card验证身份证号码的正确性,不能仅仅通过正则表达式来验证,我们都知道我国的身份证一共是18位,由十七位数字本体码和一位
- 在Python所有的数据结构中,list具有重要地位,并且非常的方便,这篇文章主要是讲解list列表的高级应用,基础知识可以查看博客。 此文
- 前言:多态的实现必须满足两个前提条件1.继承:多态一定是发生在子类和父类之间2.重写:多态子类重写了父类的方法记住这两点再结合代码示例有助于
- 在判断列表是否为空时,你更喜欢哪种方式?决定因素是什么?在 Python 中有很多检查列表是否是空的方式,在讨论解决方案前,先说一下不同方法
- 本文实例讲述了Java常用正则表达式验证类。分享给大家供大家参考,具体如下:package com.fsti.icop.util.regex
- 前言 MySQL 5.5版本之前默认的复制是异步(Asynchronous )模式的, MySQL 5
- 本文属于JavaScript的基础技能. 我们将学习结合/合并两个JS数组的各种常用方法,并比较各种方法的优缺点.我们先来看看具体的场景:v