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

这是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、数据分析、特征工程、机器学习、深度学习、人工智能系列基础知识与案例。

原文链接:

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

相关文章

Python 多任务编程

多任务的介绍利用现学知识能够让两个函数或者方法同时执行吗?不能,因为之前写的程序都是单任务的,也就是说一个函数或者方法执行完成另外一个函数或者方法才能执行,要想实现这种操作就需要使用多任务多任务的最大...

Python实现多进程的四种方式

方式一: os.fork()# -*- coding:utf-8 -*- """ pid=os.fork() 1.只用在Unix系统中有效,Windows系统中无效 2...

python多任务编程

Process进程类的说明Process([group [, target [, name [, args [, kwargs]]]]])group:指定进程组,目前只能使用Nonetarget:执行...

python多进程的分布式任务调度应用场景及示例

多进程的分布式任务调度可以应用于以下场景:分布式爬虫:import multiprocessing import requests def crawl(url): response = re...

Python中的多进程详解,让你的程序更快更强!

Python是一门高级编程语言,拥有简单易用、面向对象、可扩展等优点,因此被广泛应用于各种领域。但是在Python中,由于GIL(全局解释器锁)的存在,导致了多线程的效率不高。因此,在某些情况下,我们...