python3-python中的多任务处理利器-协程的使用(一),asyncio模块的使用

一、协程

协程的概念

协程,又称微线程,纤程,也称为用户级线程,在不开辟线程的基础上完成多任务,也就是在单线程的情况下完成多任务,多个任务按照一定顺序交替执行 通俗理解只要在def里面只看到一个yield关键字表示就是协程。
协程是也是实现多任务的一种方式。

进程,线程和协程

  • 进程是系统为cpu进行资源分配的单位;有进程号,用ps命令可以看到当前系统中的进程;进程切换需要的资源最大,效率很低;
  • 线程是操作系统调度的单位,是真正的执行单元;线程切换需要的资源一般,效率一般(当然了在不考虑GIL的情况下);
  • 协程切换任务资源很小,效率高。
    多进程、多线程根据cpu核数不一样可能是并行的,但是协程是在一个线程中 所以是并发。

协程的优缺点

  • 优点

    a、最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。

    b、不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

  • 缺点

    a、无法利用多核CPU,协程的本质是单个线程,它不能同时将多个CPU的多个核心使用上,失去了标准线程使用多CPU的能力。

    b、进行阻塞操作(操作IO)会阻塞整个程序

二、同步与异步

1、同步与异步的概念

  • 前言

    python由于GIL(全局锁)的存在,不能发挥多核的优势,其性能一直饱受诟病。然而在IO密集型的网络编程里,异步处理比同步处理能提升成百上千倍的效率

    IO密集型就是磁盘的读取数据和输出数据非常大的时候就是属于IO密集型
    由于IO操作的运行时间远远大于cpu、内存运行时间,所以任务的大部分时间都是在等待IO操作完成,IO的特点是cpu消耗小,所以,IO任务越多,cpu效率越高,当然不是越多越好,有一个极限值。

  • 同步

    指完成事务的逻辑,先执行第一个事务,如果阻塞了,会一直等待,直到这个事务完成,再执行第二个事务,顺序执行

  • 异步

    是和同步相对的,异步是指在处理调用这个事务的之后,不会等待这个事务的处理结果,直接处理第二个事务去了,通过状态、通知、回调来通知调用者处理结果

2、同步与异步代码

  • 同步

    import time
    
    def run(index):
        print("lucky is a good man", index)
        time.sleep(2)
        print("lucky is a nice man", index)
    
    for i in range(1, 5):
        run(i)
    
  • 异步

    说明:后面的课程中会使用到asyncio模块,现在的目的是使同学们理解异步思想

    import time
    import asyncio
    
    
    async def run(i):
        print("lucky is a good man", i)
        # 模拟一个耗时IO
        await asyncio.sleep(2)
        print("lucky is a nice man", i)
    
    
    def test():
        loop = asyncio.get_event_loop()
        tasks = []
        t1 = time.time()
    
        for url in range(1, 5):
            coroutine = run(url)
            task = asyncio.ensure_future(coroutine)
            tasks.append(task)
        loop.run_until_complete(asyncio.wait(tasks))
        t2 = time.time()
        print("总耗时:%.2f" % (t2 - t1))
    if __name__ == "__main__":
        test()
    
  • 协程中yield的运用

    说明:使用yield来实现一个简单的协程

    import time
    def work1():
      while True:
          print("----work1---")
          yield
          time.sleep(0.5)
    def work2():
      while True:
          print("----work2---")
          yield
          time.sleep(0.5)
    
    def test():
      w1 = work1()
      w2 = work2()
      while True:
          next(w1)
          next(w2)
    
    if __name__ == "__main__":
      test()
    
    

运行结果:

----work1—
----work2—
----work1—
----work2—
----work1—
----work2—

协程之间执行任务按照一定顺序交替执行。

三、asyncio模块

1、概述

  • asyncio模块

    是python3.4版本引入的标准库,直接内置了对异步IO的操作

  • 编程模式

    是一个消息循环,我们从asyncio模块中直接获取一个EventLoop的引用,然后把需要执行的协程扔到EventLoop中执行,就实现了异步IO

  • 说明

    到目前为止实现协程的不仅仅只有asyncio,tornado和gevent都实现了类似功能

  • 关键字的说明

    关键字 说明
    event_loop 消息循环,程序开启一个无限循环,把一些函数注册到事件循环上,当满足事件发生的时候,调用相应的协程函数
    coroutine 协程对象,指一个使用async关键字定义的函数,它的调用不会立即执行函数,而是会返回一个协程对象。协程对象需要注册到事件循环,由事件循环调用
    task 任务,一个协程对象就是一个原生可以挂起的函数,任务则是对协程进一步封装,其中包含了任务的各种状态
    async/await python3.5用于定义协程的关键字,async定义一个协程,await用于挂起阻塞的异步调用接口

2、asyncio基本使用

  • 定义一个协程

    import asyncio
    import time
    
    # 通过async关键字定义了一个协程,协程是不能直接运行的,需要将协程放到消息循环中
    async def run(x):
        print("waiting:%d"%x)
        await asyncio.sleep(x)
        print("结束run")
    
    #得到一个协程对象
    coroutine = run(2)
    asyncio.run(coroutine)
    

    等同于

    import asyncio
    import time
    
    # 通过async关键字定义了一个协程,协程是不能直接运行的,需要将协程放到消息循环中
    async def run(x):
        print("waiting:%d"%x)
        await asyncio.sleep(x)
        print("结束run")
    
    #得到一个协程对象
    coroutine = run(2)
    
    
    # 创建一个消息循环
    loop = asyncio.get_event_loop()
    
    #将协程对象加入到消息循环
    loop.run_until_complete(coroutine)
    
  • 创建一个任务

    import asyncio
    import time
    
    async def run(x):
        print("waiting:%d"%x)
        await asyncio.sleep(x)
        print("结束run")
    
    coroutine = run(2)
    #创建任务
    task = asyncio.ensure_future(coroutine)
    
    loop = asyncio.get_event_loop()
    
    # 将任务加入到消息循环
    loop.run_until_complete(task)
    
  • 阻塞和await

    async可以定义协程,使用await可以针对耗时操作进行挂起,就与生成器的yield一样,函数交出控制权。协程遇到await,消息循环会挂起该协程,执行别的协程,直到其他协程也会挂起或者执行完毕,在进行下一次执行

  • 获取返回值

    import time
    import asyncio
    
    async def run(url):
        print("开始向'%s'要数据……"%(url))
        # 向百度要数据,网络IO
        await asyncio.sleep(5)
        data = "'%s'的数据"%(url)
        print("给你数据")
        return data
    
    # 定义一个回调函数
    def call_back(future):
        print("call_back:", future.result())
    
    coroutine = run("百度")
    # 创建一个任务对象
    task = asyncio.ensure_future(coroutine)
    
    # 给任务添加回调,在任务结束后调用回调函数
    task.add_done_callback(call_back)
    
    loop = asyncio.get_event_loop()
    loop.run_until_complete(task)
    

3、多任务

  • 同步

    同时请求"百度", “阿里”, “腾讯”, "新浪"四个网站,假设响应时长均为2秒

    import time
    
    def run(url):
        print("开始向'%s'要数据……"%(url))
        # 向百度要数据,网络IO
        time.sleep(2)
        data = "'%s'的数据"%(url)
        return data
    
    if __name__ == "__main__":
        t1 = time.time()
        for url in ["百度", "阿里", "腾讯", "新浪"]:
            print(run(url))
        t2 = time.time()
        print("总耗时:%.2f"%(t2-t1))
    
  • 异步

    同时请求"百度", “阿里”, “腾讯”, "新浪"四个网站,假设响应时长均为2秒

    使用ensure_future创建多任务

    import time
    import asyncio
    
    async def run(url):
        print("开始向'%s'要数据……"%(url))
        await asyncio.sleep(2)
        data = "'%s'的数据"%(url)
        return data
    
    def call_back(future):
        print("call_back:", future.result())
    
    def  test():
        loop = asyncio.get_event_loop()
        tasks = []
        t1 = time.time()
        
        for url in ["百度", "阿里", "腾讯", "新浪"]:
            coroutine = run(url)
            task = asyncio.ensure_future(coroutine)
            task.add_done_callback(call_back)
            tasks.append(task)
            
        # 同时添加4个异步任务
        # asyncio.wait(tasks) 将任务的列表又变成 
        loop.run_until_complete(asyncio.wait(tasks))
    
        t2 = time.time()
        print("总耗时:%.2f" % (t2 - t1))
    if __name__ == "__main__":
      test()
    
    • 封装成异步函数

      import time
      import asyncio
      
      
      async def run(url):
          print("开始向'%s'要数据……" % (url))
          await asyncio.sleep(2)
          data = "'%s'的数据" % (url)
          return data
      
      
      def call_back(future):
          print("call_back:", future.result())
      
      
      async def main():
          tasks = []
          t1 = time.time()
      
          for url in ["百度", "阿里", "腾讯", "新浪"]:
              coroutine = run(url)
              task = asyncio.ensure_future(coroutine)
              task.add_done_callback(call_back)
              tasks.append(task)
      
          # 同时添加4个异步任务
          await asyncio.wait(tasks)
          t2 = time.time()
          print("总耗时:%.2f" % (t2 - t1))
      
      if __name__ == "__main__":
          loop = asyncio.get_event_loop()
          loop.run_until_complete(main())
      

    使用loop.create_task创建多任务

    import time
    import asyncio
    
    
    async def run(url):
        print("开始向'%s'要数据……" % (url))
        await asyncio.sleep(2)
        data = "'%s'的数据" % (url)
        return data
    
    
    def call_back(future):
        print("call_back:", future.result())
    
    
    def test():
        loop = asyncio.get_event_loop()
        tasks = []
        t1 = time.time()
    
        for url in ["百度", "阿里", "腾讯", "新浪"]:
            coroutine = run(url)
            # task = asyncio.ensure_future(coroutine)
            task = loop.create_task(coroutine)
            task.add_done_callback(call_back)
            tasks.append(task)
            # 同时添加4个异步任务
        loop.run_until_complete(asyncio.wait(tasks))
    
        t2 = time.time()
        print("总耗时:%.2f" % (t2 - t1))
    if __name__ == "__main__":
          test()
    
    • 封装成异步函数

      import time
      import asyncio
      
      
      async def run(url):
          print("开始向'%s'要数据……" % (url))
          await asyncio.sleep(2)
          data = "'%s'的数据" % (url)
          return data
      
      
      def call_back(future):
          print("call_back:", future.result())
      
      
      async def main():
          tasks = []
          t1 = time.time()
          for url in ["百度", "阿里", "腾讯", "新浪"]:
              coroutine = run(url)
              task = loop.create_task(coroutine)
              task.add_done_callback(call_back)
              tasks.append(task)
          # 同时添加4个异步任务
          await asyncio.wait(tasks)
          t2 = time.time()
          print("总耗时:%.2f" % (t2 - t1))
      
      if __name__ == "__main__":
        	# asyncio.run(main())
          loop = asyncio.get_event_loop()
          loop.run_until_complete(main())
      

    使用asyncio.create_task创建多任务

    import time
    import asyncio
    
    
    async def run(url):
        print("开始向'%s'要数据……" % (url))
        await asyncio.sleep(2)
        data = "'%s'的数据" % (url)
        return data
    
    
    def call_back(future):
        print("call_back:", future.result())
    
    
    async def main():
        tasks = []
        t1 = time.time()
        for url in ["百度", "阿里", "腾讯", "新浪"]:
            coroutine = run(url)
            task = asyncio.create_task(coroutine)
            task.add_done_callback(call_back)
            tasks.append(task)
        # 同时添加4个异步任务
        await asyncio.wait(tasks)
        t2 = time.time()
        print("总耗时:%.2f" % (t2 - t1))
    
    if __name__ == "__main__":
        # asyncio.run(main())
        loop = asyncio.get_event_loop()
        loop.run_until_complete(main())
    

4、Task 概念及用法

  • Task,是 python 中与事件循环进行交互的一种主要方式。

    创建 Task,意思就是把协程封装成 Task 实例,并追踪协程的 运行 / 完成状态,用于未来获取协程的结果。

  • Task 核心作用: 在事件循环中添加多个并发任务;

    具体来说,是通过 asyncio.create_task() 创建 Task,让协程对象加入时事件循环中,等待被调度执行。

    **注意:**Python 3.7 以后的版本支持 asyncio.create_task() ,在此之前的写法为 loop.create_task() ,开发过程中需要注意代码写 法对不同版本 python 的兼容性。

  • 需要指出的是,协程封装为 Task 后不会立马启动,当某个代码 await 这个 Task 的时候才会被执行。

    当多个 Task 被加入一个 task_list 的时候,添加 Task 的过程中 Task 不会执行,必须要用 await asyncio.wait() await asyncio.gather() 将 Task 对象加入事件循环中异步执行。

  • 一般在开发中,常用的写法是这样的:

    – 先创建 task_list 空列表;
    – 然后用 asyncio.create_task() 创建 Task;

    – 再把 Task 对象加入 task_list ;

    – 最后使用 await asyncio.wait 或 await asyncio.gather 将 Task 对象加入事件循环中异步执行。

    注意: 创建 Task 对象时,除了可以使用 asyncio.create_task() 之外,还可以用最低层级的 loop.create_task() 或 asyncio.ensure_future() ,他们都可以用来创建 Task 对象,其中关于 ensure_future 相关内容本文接下来会一起讲。

  • Task 简单用法

import asyncio

async def func():
    print(1)
    await asyncio.sleep(2)
    print(2)
    return "test"


async def main():
    print("main start")

    # python 3.7及以上版本的写法
    task1 = asyncio.create_task(func())
    task2 = asyncio.create_task(func())

    # python3.7以前的写法
    # task1 = asyncio.ensure_future(func())
    # task2 = asyncio.ensure_future(func())
    print("main end")

    ret1 = await task1
    ret2 = await task2

    print(ret1, ret2)


# python3.7以后的写法
asyncio.run(main())

# python3.7以前的写法
# loop = asyncio.get_event_loop()
# loop.run_until_complete(main())

"""
在创建task的时候,就将创建好的task添加到了时间循环当中,所以说必须得有时间循环,才可以创建task,否则会报错
"""
  • task用法实例

    import asyncio
    import arrow
    
    def current_time():
        '''
        获取当前时间
        :return:
        '''
        cur_time = arrow.now().to('Asia/Shanghai').format('YYYY-MM-DD HH:mm:ss')
        return cur_time
    
    
    async def func(sleep_time):
        func_name_suffix = sleep_time # 使用 sleep_time (函数 I/O 等待时长)作为函数名后缀,以区分任务对象
        print(f"[{current_time()}] 执行异步函数 {func.__name__}-{func_name_suffix}")
        await asyncio.sleep(sleep_time)
        print(f"[{current_time()}]函数{func.__name__}-{func_name_suffix} 执行完毕")
        return f"【[{current_time()}] 得到函数 {func.__name__}-{func_name_suffix} 执行结果】"
    
    async def run():
        task_list = []
        for i in range(5):
            task = asyncio.create_task(func(i))
            task_list.append(task)
        done, pending = await asyncio.wait(task_list)
        for done_task in done:
            print((f"[{current_time()}]得到执行结果 {done_task.result()}"))
    def testcase_a():
        loop = asyncio.get_event_loop()
        loop.run_until_complete(run())
    
    if __name__ == '__main__':
        testcase_a()
    
  • 代码执行结果如下:

% python a.py
[2022-10-24 16:17:00] 执行异步函数 func-0
[2022-10-24 16:17:00] 执行异步函数 func-1
[2022-10-24 16:17:00] 执行异步函数 func-2
[2022-10-24 16:17:00] 执行异步函数 func-3
[2022-10-24 16:17:00] 执行异步函数 func-4
[2022-10-24 16:17:00]函数func-0 执行完毕
[2022-10-24 16:17:01]函数func-1 执行完毕
[2022-10-24 16:17:02]函数func-2 执行完毕
[2022-10-24 16:17:03]函数func-3 执行完毕
[2022-10-24 16:17:04]函数func-4 执行完毕
[2022-10-24 16:17:04]得到执行结果 【[2022-10-24 16:17:01] 得到函数 func-1 执行结果】
[2022-10-24 16:17:04]得到执行结果 【[2022-10-24 16:17:04] 得到函数 func-4 执行结果】
[2022-10-24 16:17:04]得到执行结果 【[2022-10-24 16:17:02] 得到函数 func-2 执行结果】
[2022-10-24 16:17:04]得到执行结果 【[2022-10-24 16:17:00] 得到函数 func-0 执行结果】
[2022-10-24 16:17:04]得到执行结果 【[2022-10-24 16:17:03] 得到函数 func-3 执行结果】

5、协程嵌套与返回值

使用async可以定义协程,协程用于耗时的io操作,我们也可以封装更多的io操作过程,这样就实现了嵌套的协程,即一个协程中await了另外一个协程,如此连接起来

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Cqv7Qc5C-1666595924161)(协程.assets/截屏2020-01-1317_34_29.png)]

import time
import asyncio

async def run(url):
    print("开始向'%s'要数据……"%(url))
    await asyncio.sleep(2)
    data = "'%s'的数据"%(url)
    return data

def call_back(future):
    print("call_back:", future.result())

async def main():
    tasks = []
    for url in ["百度", "阿里", "腾讯", "新浪"]:
        coroutine = run(url)
        task = asyncio.ensure_future(coroutine)
        # task.add_done_callback(call_back)
        tasks.append(task)

    # #1、可以没有回调函数
    # dones, pendings = await asyncio.wait(tasks)
    # #处理数据,类似回调,建议使用回调
    # for t in dones:
    #     print("数据:%s"%(t.result()))

    # #2、可以没有回调函数
    # results = await asyncio.gather(*tasks)
    # # 处理数据,类似回调,建议使用回调
    # for result in results:
    #     print("数据:%s"%(result))


    # 3、有无回调函数均可以
    # return await asyncio.wait(tasks)


    # 4、有无回调函数均可以
    # return await asyncio.gather(*tasks)



if __name__ == "__main__":
    t1 = time.time()
    loop = asyncio.get_event_loop()
    #1、
    # loop.run_until_complete(main())
    # asyncio.run(main()) # 等同于上面两行代码

    #2、
    # loop.run_until_complete(main())

    # # 3、
    # dones, pendings = loop.run_until_complete(main())
    # #处理数据,类似回调,建议使用回调
    # for t in dones:
    #     print("数据:%s"%(t.result()))

    # 4、
    # results = loop.run_until_complete(main())
    # for result in results:
    #     print("数据:%s"%(result))

    t2 = time.time()
    print("总耗时:%.2f" % (t2 - t1))
  • asyncio.wait和asyncio.gather的异同

    1. 异同点综述

    相同:从功能上看, asyncio.wait 和 asyncio.gather 实现的效果是相同的,都是把所有 Task 任务结果收集起来。

    不同: asyncio.wait 会返回两个值: done 和 pending , done 为已完成的协程 Task , pending 为超时未完成的协程 Task ,需通过 future.result 调用 Task 的 result ;而 asyncio.gather 返回的是所有已完成 Task 的 result ,不需要再进行调用或其他操作,就可以得到全部结果。

    1. asyncio.wait 用法:

    最常见的写法是: await asyncio.wait(task_list) 。

    import asyncio
    import arrow
    
    def current_time():
        '''
        获取当前时间
        :return:
         '''
        cur_time = arrow.now().to('Asia/Shanghai').format('YYYY-MM-DD HH:mm:ss')
        return cur_time
    
    async def func(sleep_time):
        func_name_suffix = sleep_time # 使用 sleep_time (函数 I/O 等待时长)作为函数名后缀,以区分任务对象
        print(f"[{current_time()}] 执行异步函数 {func.__name__}-{func_name_suffix}")
        await asyncio.sleep(sleep_time)
        print(f"[{current_time()}]函数{func.__name__}-{func_name_suffix} 执行完毕")
        return f"【[{current_time()}] 得到函数 {func.__name__}-{func_name_suffix} 执行结果】"
    
    async def run():
        task_list = []
        for i in range(5):
            task = asyncio.create_task(func(i))
            task_list.append(task)
    
        done, pending = await asyncio.wait(task_list)
        for done_task in done:
            print((f"[{current_time()}]得到执行结果 {done_task.result()}"))
    
    def testcase_b():
        loop = asyncio.get_event_loop()
        loop.run_until_complete(run())
    
    if __name__ == '__main__':
        testcase_b()
    

    代码执行结果如下:

% python3 b.py
[2022-10-24 16:17:45] 执行异步函数 func-0
[2022-10-24 16:17:45] 执行异步函数 func-1
[2022-10-24 16:17:45] 执行异步函数 func-2
[2022-10-24 16:17:45] 执行异步函数 func-3
[2022-10-24 16:17:45] 执行异步函数 func-4
[2022-10-24 16:17:45]函数func-0 执行完毕
[2022-10-24 16:17:46]函数func-1 执行完毕
[2022-10-24 16:17:47]函数func-2 执行完毕
[2022-10-24 16:17:48]函数func-3 执行完毕
[2022-10-24 16:17:49]函数func-4 执行完毕
[2022-10-24 16:17:49]得到执行结果 【[2022-10-24 16:17:46] 得到函数 func-1 执行结果】
[2022-10-24 16:17:49]得到执行结果 【[2022-10-24 16:17:49] 得到函数 func-4 执行结果】
[2022-10-24 16:17:49]得到执行结果 【[2022-10-24 16:17:47] 得到函数 func-2 执行结果】
[2022-10-24 16:17:49]得到执行结果 【[2022-10-24 16:17:45] 得到函数 func-0 执行结果】
[2022-10-24 16:17:49]得到执行结果 【[2022-10-24 16:17:48] 得到函数 func-3 执行结果】

  1. asyncio.gather 用法:

最常见的用法是: await asyncio.gather(*task_list) ,注意这里 task_list 前面有一个 *

import asyncio
import arrow

def current_time():
    '''
    获取当前时间
    :return:
     '''
    cur_time = arrow.now().to('Asia/Shanghai').format('YYYY-MM-DD HH:mm:ss')
    return cur_time

async def func(sleep_time):
    func_name_suffix = sleep_time # 使用 sleep_time (函数 I/O 等待时长)作为函数名后缀,以区分任务对象
    print(f"[{current_time()}] 执行异步函数 {func.__name__}-{func_name_suffix}")
    await asyncio.sleep(sleep_time)
    print(f"[{current_time()}]函数{func.__name__}-{func_name_suffix} 执行完毕")
    return f"【[{current_time()}] 得到函数 {func.__name__}-{func_name_suffix} 执行结果】"

async def run():
    task_list = []
    for i in range(5):
        task = asyncio.create_task(func(i))
        task_list.append(task)

    results = await asyncio.gather(*task_list)
    for result in results:
        print((f"[{current_time()}]得到执行结果 {result}"))

def testcase_c():
    loop = asyncio.get_event_loop()
    loop.run_until_complete(run())

if __name__ == '__main__':
    testcase_c()

代码执行结果如下:

% python3 c.py
[2022-10-24 16:18:23] 执行异步函数 func-0
[2022-10-24 16:18:23] 执行异步函数 func-1
[2022-10-24 16:18:23] 执行异步函数 func-2
[2022-10-24 16:18:23] 执行异步函数 func-3
[2022-10-24 16:18:23] 执行异步函数 func-4
[2022-10-24 16:18:23]函数func-0 执行完毕
[2022-10-24 16:18:24]函数func-1 执行完毕
[2022-10-24 16:18:25]函数func-2 执行完毕
[2022-10-24 16:18:26]函数func-3 执行完毕
[2022-10-24 16:18:27]函数func-4 执行完毕
[2022-10-24 16:18:27]得到执行结果 【[2022-10-24 16:18:23] 得到函数 func-0 执行结果】
[2022-10-24 16:18:27]得到执行结果 【[2022-10-24 16:18:24] 得到函数 func-1 执行结果】
[2022-10-24 16:18:27]得到执行结果 【[2022-10-24 16:18:25] 得到函数 func-2 执行结果】
[2022-10-24 16:18:27]得到执行结果 【[2022-10-24 16:18:26] 得到函数 func-3 执行结果】
[2022-10-24 16:18:27]得到执行结果 【[2022-10-24 16:18:27] 得到函数 func-4 执行结果】

你可能感兴趣的:(Python学习笔记,1024程序员节,python协程,python多任务,asyncio,python同步与异步)