Python—协程(Coroutine)

2024-06-04 1953阅读

文档结构

  • 1、概念简介
  • 2、使用场景
  • 3、实现方式
  • 4、代码案例
  • 5、注意事项

    参考手册:https://docs.python.org/zh-cn/3.10/library/asyncio-task.html

    1、概念简介

    协程不是被操作系统内核所管理,而完全是由程序所控制(也就是在用户态执行),是一种用户状态内的上下文切换技术,其实就是通过一个线程实现代码块相互切换执行,因此也称为轻量级的线程。

    协程的切换完全由程序控制,而非通过操作系统内核来实现,因此对资源的开销更小;

    2、使用场景

    通过 async & awiat 实现异步编程,是Python实现异步操作的主流技术;

    如果一个对象可以在 await 语句中使用,那么它就是 可等待 对象。许多 asyncio API 都被设计为接受可等待对象。

    可等待 对象有三种主要类型: Coroutine(协程),task(任务) 和 Future;

    3、实现方式

    1)greenlet:一个第三方模块,需要提前安装 pip3 install greenlet才能使用;

    2)yield生成器:借助生成器的特点亦可以实现协程代码;

    3)asyncio:在python3.4 种引入的模块,用于编写协程代码;

    说明:主要通过装饰器 @asyncio.coroutine 来实现协程函数定义;Python3.8之后 @asyncio.coroutine 装饰器会被移除,推荐使用async & awit 关键字实现协程代码。

    4)async & awiat:在python3.5中引入的两个关键字,结合asyncio模块使用;

    4、代码案例

    场景:目前解析一个 xml文件,需要解析的节点处理为一个列表,然后希望并发解析列表里的节点数据

    实现方式1:多线程方式

    1)创建函数 parseNode(node, delay);

    2)循环该节点列表,每个节点分配一个线程去执行 parseNode(node, delay),同时将该线程加入线程列表 thread_list;

    3)循环线程列表,执行 th.join(),使主线程等待每个线程执行完成;

    4)执行主线程后续步骤;

    实现方式2:协程方式

    代码1:直接执行协程对象

    # -*- coding= utf-8 -*-
    """
    @DevTool  : PyCharm
    @Author   : xxx
    @DateTime : 2024/1/29 15:36
    @FileName : coroutineDemo.py
    """
    import asyncio
    import time
    async def parseNode(th_n: int, delay: int):
        print("协程{}-开始执行!".format(th_n))
        await asyncio.sleep(delay)
        print("协程{}-执行结束,耗时{}秒!".format(th_n, delay))
    node_list = [x for x in range(1, 21)]
    print("主线程-运行开始")
    v_start = time.time()
    for x in node_list:
        asyncio.run(parseNode(x, x))
    v_end = time.time()
    print("主线程-运行结束,耗时{}秒!".format(round((v_end - v_start), 2)))
    

    输出结果1:

    E:\PythonProject\dataSpider\venv\Scripts\python.exe E:/PythonProject/dataSpider/coroutineDemo.py
    主线程-运行开始
    协程1-开始执行!
    协程1-执行结束,耗时1秒!
    协程2-开始执行!
    协程2-执行结束,耗时2秒!
    协程3-开始执行!
    协程3-执行结束,耗时3秒!
    协程4-开始执行!
    协程4-执行结束,耗时4秒!
    协程5-开始执行!
    协程5-执行结束,耗时5秒!
    协程6-开始执行!
    协程6-执行结束,耗时6秒!
    协程7-开始执行!
    协程7-执行结束,耗时7秒!
    协程8-开始执行!
    协程8-执行结束,耗时8秒!
    协程9-开始执行!
    协程9-执行结束,耗时9秒!
    协程10-开始执行!
    协程10-执行结束,耗时10秒!
    协程11-开始执行!
    协程11-执行结束,耗时11秒!
    协程12-开始执行!
    协程12-执行结束,耗时12秒!
    协程13-开始执行!
    协程13-执行结束,耗时13秒!
    协程14-开始执行!
    协程14-执行结束,耗时14秒!
    协程15-开始执行!
    协程15-执行结束,耗时15秒!
    协程16-开始执行!
    协程16-执行结束,耗时16秒!
    协程17-开始执行!
    协程17-执行结束,耗时17秒!
    协程18-开始执行!
    协程18-执行结束,耗时18秒!
    协程19-开始执行!
    协程19-执行结束,耗时19秒!
    协程20-开始执行!
    协程20-执行结束,耗时20秒!
    主线程-运行结束,耗时210.15秒!
    Process finished with exit code 0
    

    从输出结果看,明显是串行执行的,下面用另外一种代码实现并行执行;

    代码2:执行代理主函数对象

    Python 在3.7 中加入了asyncio.create_task() 函数,低版本的Python可以改用低层级的 asyncio.ensure_future() 函数;

    asyncio.create_task() 函数用来并发运行作为 asyncio 任务 的多个协程。

    # -*- coding= utf-8 -*-
    """
    @DevTool  : PyCharm
    @Author   : xxx
    @DateTime : 2024/1/29 15:36
    @FileName : coroutineDemo.py
    """
    import asyncio
    import time
    async def parseNode(th_n: int, delay: int):
        print("协程{}-开始执行!".format(th_n))
        await asyncio.sleep(delay)
        print("协程{}-执行结束,耗时{}秒!".format(th_n, delay))
    async def proxyMain():
        exec_list = [x for x in range(1, 21)]
        task_list = list() # 任务列表
        for x in exec_list:
            task = asyncio.create_task(parseNode(x, x))
            task_list.append(task) # 将每个任务都放入任务列表
        for y in task_list:
            await y # 使主线程等待每个任务执行完成
    v_start = time.time()
    print("主线程-运行开始")
    asyncio.run(proxyMain())
    v_end = time.time()
    print("主线程-运行结束,耗时{}秒!".format(round((v_end - v_start), 2)))
    

    输出结果2:

    E:\PythonProject\dataSpider\venv\Scripts\python.exe E:/PythonProject/dataSpider/encryptDemo.py
    主线程-运行开始
    协程1-开始执行!
    协程2-开始执行!
    协程3-开始执行!
    协程4-开始执行!
    协程5-开始执行!
    协程6-开始执行!
    协程7-开始执行!
    协程8-开始执行!
    协程9-开始执行!
    协程10-开始执行!
    协程11-开始执行!
    协程12-开始执行!
    协程13-开始执行!
    协程14-开始执行!
    协程15-开始执行!
    协程16-开始执行!
    协程17-开始执行!
    协程18-开始执行!
    协程19-开始执行!
    协程20-开始执行!
    协程1-执行结束,耗时1秒!
    协程2-执行结束,耗时2秒!
    协程3-执行结束,耗时3秒!
    协程4-执行结束,耗时4秒!
    协程5-执行结束,耗时5秒!
    协程6-执行结束,耗时6秒!
    协程7-执行结束,耗时7秒!
    协程8-执行结束,耗时8秒!
    协程9-执行结束,耗时9秒!
    协程10-执行结束,耗时10秒!
    协程11-执行结束,耗时11秒!
    协程12-执行结束,耗时12秒!
    协程13-执行结束,耗时13秒!
    协程14-执行结束,耗时14秒!
    协程15-执行结束,耗时15秒!
    协程16-执行结束,耗时16秒!
    协程17-执行结束,耗时17秒!
    协程18-执行结束,耗时18秒!
    协程19-执行结束,耗时19秒!
    协程20-执行结束,耗时20秒!
    主线程-运行结束,耗时20.01秒!
    Process finished with exit code 0
    

    从输出结果看,执行时间从之前的 210秒 降到了20秒;

    说明:上述代码中必须先全部使用 create_task任务,然后在调用每个任务;

    代码3:asyncio.gather(task)任务组

    # -*- coding= utf-8 -*-
    """
    @DevTool  : PyCharm
    @Author   : xxx
    @DateTime : 2024/1/29 15:36
    @FileName : coroutineDemo.py
    """
    import asyncio
    import time
    async def parseNode(th_n: int, delay: int):
        print("协程{}-开始执行!".format(th_n))
        await asyncio.sleep(delay)
        print("协程{}-执行结束,耗时{}秒!".format(th_n, delay))
    async def proxyMain():
        exec_list = [x for x in range(1, 21)]
        task_list = list()  # 任务列表
        task_set = asyncio.gather()
        for x in exec_list:
            task = asyncio.create_task(parseNode(x, x))
            task_set = asyncio.gather(task)  # 将每个任务都放入任务列表
        await task_set
    v_start = time.time()
    print("主线程-运行开始")
    asyncio.run(proxyMain())
    v_end = time.time()
    print("主线程-运行结束,耗时{}秒!".format(round((v_end - v_start), 2)))
    

    代码4:

    python 在 3.11 版本加入了 asyncio.TaskGroup类,提供了create_task() 更现代化的替代;

    # -*- coding= utf-8 -*-
    """
    @DevTool  : PyCharm
    @Author   : gzh
    @DateTime : 2024/2/1 15:57
    @FileName : coroutDemo.py
    """
    import asyncio
    import time
    async def parseNode(th_n: int, delay: int):
        print("协程{}-开始执行!".format(th_n))
        await asyncio.sleep(delay)
        print("协程{}-执行结束,耗时{}秒!".format(th_n, delay))
    async def proxyMain():
        exec_list = [x for x in range(1, 21)]
        async with asyncio.TaskGroup() as tg:
            for x in exec_list:
               task =  tg.create_task(parseNode(x, x))
    v_start = time.time()
    print("主线程-运行开始")
    asyncio.run(proxyMain())
    v_end = time.time()
    print("主线程-运行结束,耗时{}秒!".format(round((v_end - v_start), 2)))
    

    5、注意事项

    1)直接调用一个使用 async 定义的函数,是不会执行的;

    2)协程执行的业务代码一般不会写在主函数中,而是定义个代理主函数;即通过一个协程调用其他协程;

    ============================================ over ==========================================


    免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们,邮箱:ciyunidc@ciyunshuju.com。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!

    目录[+]