【Python程序开发系列】一文教你使用协程处理多任务(案例+源码)

2024-01-02 11:16:25

这是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任务

2.1 创建任务

task:任务,对协程对象的进一步封装,包含任务的各个状态。

task = asyncio.create_task(func(参数列表))
task = asyncio.ensure_future(func(参数列表))

2.2 创建多任务

tasks = [asyncio.create_task(func(1)), 
         asyncio.create_task(func(2)),
         asyncio.create_task(func(3))]

将协程当做任务添加到事件循环的任务列表,然后事件循环检测列表中的协程是否 已准备就绪(默认可理解为就绪状态),如果准备就绪则执行其内部代码。当前协程(任务)挂起过程中事件循环可以去执行其他的协程(任务),当前协程IO处理完成时,可以再次切换回来执行await之后的代码。

三、并发执行多个协程(coroutine)的函数

3.1 asyncio.gather

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协程都会抛出异常。

3.2 asyncio.wait

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、机器学习、深度学习、人工智能系列基础知识与应用案例。致力于只做原创,以最简单的方式理解和学习,关注我一起交流成长。

文章来源:https://blog.csdn.net/sinat_41858359/article/details/135333007
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。