这是Python程序开发系列原创文章,我的第188篇原创文章。
? 前文回顾:
? ?Python语言高级实战-基于协程的方式来实现异步并发编程(附源码和实现效果)
?【Python程序开发系列】进程、线程、协程?一文全面梳理多任务并发编程基本概念
异步编程:
????????异步编程是通过一个线程在IO等待时间去执行其他任务,从而实现并发。以代码实现下载 url_list 中的图片为例:
同步编程,按照顺序逐一排队执行,如果图片下载时间为2分钟,那么全部执行完则需要6分钟。
异步编程,几乎同时发出了3个下载任务的请求(遇到IO请求自动切换去发送其他任务请求),如果图片下载时间为2分钟,那么全部执行完毕也大概需要2分钟左右就可以了。
协程:
????????协程:本质是一个异步函数。
????????协程对象:调用异步函数所返回的对象。
????????利用协程在IO等待时间就去切换执行其他任务,当IO操作结束后再自动回调,那么就会大大节省资源并提供性能,从而实现异步编程(不等待任务结束就可以去执行其他代码),因此协程在处理IO密集型任务方面非常高效。
asyncio库:
????????asyncio即Asynchronous I/O是python一个用来处理并发(concurrent)事件的包,是很多python异步架构的基础,多用于处理高并发网络请求方面的问题。简单来说,asyncio解决的是:IO阻塞导致cpu利用率降低的问题。?
????????async:用于声明一个函数为异步函数,即该函数内部可能会使用到异步操作。
????????await:用于等待一个异步操作完成。当在一个异步函数中使用await关键字时,该函数会暂停执行,直到等待的异步操作完成并返回结果后,才继续执行后续代码。在特殊函数内部,凡是阻塞操作前都必须使用await进行修饰。异步程序执行到某一步时需要等待很长时间,就将此挂起,去执行其他的异步程序。
????????asyncio.run() :运行协程
task:任务,对协程对象的进一步封装,包含任务的各个状态。
task = asyncio.create_task(func(参数列表))
task = asyncio.ensure_future(func(参数列表))
tasks = [asyncio.create_task(func(1)),
asyncio.create_task(func(2)),
asyncio.create_task(func(3))]
将协程当做任务添加到事件循环的任务列表,然后事件循环检测列表中的协程是否 已准备就绪(默认可理解为就绪状态),如果准备就绪则执行其内部代码。当前协程(任务)挂起过程中事件循环可以去执行其他的协程(任务),当前协程IO处理完成时,可以再次切换回来执行await之后的代码。
tasks = [task1,task2,task3]
results = await asyncio.gather(*tasks)
results = await asyncio.gather(task1,task2,task3)
asyncio.gather
接收一个协程列表作为参数,并返回一个新的协程。当所有传入的协程都完成时,这个新的协程也会完成。
asyncio.gather
返回一个新的协程,可以通过调用其result()
方法获取所有协程的结果。(似乎直接返回所有已经完成的协程的结果)
asyncio.gather
会等待所有传入的协程都完成,如果其中任何一个协程抛出异常,那么整个asyncio.gather
协程都会抛出异常。
await asyncio.wait(tasks) #对协成tasks阻塞的结果收集,得到一个对象
#获取每个任务对象的结果值
for task in tasks:
print(task.done(),task.result())
asyncio.wait
接收一个或多个协程以及一个超时时间作为参数。它会阻塞主线程,直到至少有一个协程完成或者超时。
asyncio.wait
返回一个包含已完成协程对象的集合,可以通过遍历这个集合来获取每个协程的结果。
asyncio.wait
只会等待至少有一个协程完成,如果其中任何一个协程抛出异常,那么asyncio.wait
会立即返回已完成的协程对象集合,而不会继续等待其他协程完成。
总结来说,asyncio.gather适用于需要等待所有协程都完成的场景,而asyncio.wait适用于只需要等待至少一个协程完成的场景。
import asyncio
import time
# 子协程:用于执行一些耗时的操作,例如网络请求、IO 操作等。
async def func(i):
print(f"任务{i}启动")
await asyncio.sleep(i) # 模拟阻塞操作
print(f"任务{i}结束")
return i * i # 返回每个任务的平方值
# 主协程:程序的起点,可以创建和管理其他子协程
async def main_1():
start = time.time()
# 显式的创建协程对象任务,并进行传参
tasks = [asyncio.create_task(func(1)),
asyncio.create_task(func(2)),
asyncio.create_task(func(3))]
res = await asyncio.gather(*tasks) #使用gather直接收集协成对象的结果
print("cost timer:", time.time() - start) # 总耗时
print(res)
# 主协程:程序的起点,可以创建和管理其他子协程
async def main_2():
start = time.time()
# 显式的创建协程对象任务,并进行传参
tasks = [asyncio.create_task(func(1)),
asyncio.create_task(func(2)),
asyncio.create_task(func(3))]
await asyncio.wait(tasks)
print("cost timer:", time.time() - start) # 总耗时
# 获取每个任务对象的结果值
for task in tasks:
print(task.done(), task.result())
if __name__ == '__main__':
asyncio.run(main_1())
asyncio.run(main_2())
main_1()执行结果如下,三个阻塞任务异步并发执行,执行时间约等于阻塞时长最长的那个任务。
main_2()执行结果如下,三个阻塞任务异步并发执行,执行时间约等于阻塞时长最长的那个任务。
本期内容就到这里,我们下期再见!需要数据集和源码的小伙伴可以关注底部公众号添加作者微信!
作者简介:
读研期间发表6篇SCI数据挖掘相关论文,现在某研究院从事数据算法相关科研工作,结合自身科研实践经历不定期分享关于Python、机器学习、深度学习、人工智能系列基础知识与应用案例。致力于只做原创,以最简单的方式理解和学习,关注我一起交流成长。