协程
作者:互联网
一 基本概念
协程(Coroutine),是一种用户态的轻量级线程,又称微线程,纤程,可以实现单线程下的并发。是一种用户态内进行上下文切换的技术,由用户程序自己控制任务调度的,简而言之,其实就是通过线程可以实现代码块相互切换执行。协程与线程、进程同属于python中实现异步多任务的常用方式。
CPU能识别的最小任务调度单位是线程,而对于CPU来说,协程是不被识别的或不可见的。
从单进程到多进程提高了 CPU 利用率;从进程到线程,降低了上下文切换的开销;而从线程到协程,进一步降低了上下文切换的开销,使得高并发的服务可以使用简单的代码写出来的。
协程实现了一个线程内多个任务交替执行
在Python中有2种方式可以实现协程:
-
python原生语法实现:生成器(yield & yield from) ----> async & await ---> asyncio(底层)
-
C语言底层模块显示:greenlet ----> gevent / eventlet
二 基于生成器实现协程【少用】
普通函数中使用了yield关键字以后,该函数就会变成生成器函数
1 # 在普通函数中使用了yield关键字以后,该函数就会变成生成器函数 2 3 def func1(): 4 print("1-1. func1任务执行了") 5 yield from func2() # 交出CPU的执行权,去迭代执行 func2生成器函数(CPU资源的让渡), 6 print("1-2. func1任务结束了") 7 8 def func2(): 9 print("2-1. func1任务执行了") 10 yield # 也进行了交出CPU的执行权(让渡) 11 print("2-2. func1任务结束了") 12 13 if __name__ == '__main__': 14 # print(func1()) # 生成器函数的返回值是一个生成器对象 15 for item in func1(): 16 item 17 18 # 实现了一个线程内多个任务交替执行,这就是协程 !!! 19 20 ''' 21 1-1. func1任务执行了 22 2-1. func1任务执行了 23 2-2. func1任务结束了 24 1-2. func1任务结束了 25 '''基于生成器实现协议
三 基于greenlet模块实现协程【少用】
1 import time 2 3 from greenlet import greenlet 4 5 6 def func1(): 7 print("1-1. func1任务执行了") # 第2步:输出 1-1 8 g2.switch(2) # 第3步:调度切换,调度执行func2,并把参数2传递到任务中 9 print("1-2. func1任务结束了") # 第7步:输出 1-2 10 11 def func2(n): 12 print(f"{n}-1. func1任务执行了") # 第4步,输出n-1 13 print(f"{n}-2. func1任务结束了") # 第5步,输出n-2 14 g1.switch() # 第6步,调度切换,调度执行func1,恢复func1的执行状态 15 16 if __name__ == '__main__': 17 # 创建2个协程,参数就是协程要执行的任务 18 g1 = greenlet(func1) 19 g2 = greenlet(func2) 20 g1.switch() # 第1步,切换协程,并且也可以传递参数到协程任务中 21 print("主程序") # 第8步,因为不再有协程需要执行了,所以主程序结束 22 23 ''' 24 1-1. func1任务执行了 25 2-1. func1任务执行了 26 2-2. func1任务结束了 27 1-2. func1任务结束了 28 主程序 29 '''基于greenlet实现协程[少用]
四 基于gevent模块实现协程调度
gevent提供的常用方法
方法 | 描述 |
---|---|
gevent.spawn(任务,任务参数) | 创建greenlet协程对象 |
gevent.spawn(任务1,任务参数).link_value(回调处理函数,函数参数) | 给协程对象注册结果回调处理函数 |
gevent.sleep(n) | 异步的阻塞,没有真正阻塞。可以被协程识别。区别于time.sleep同步阻塞,不可被协程识别 |
gevent.joinall | 基于libev事件循环实现多个协程阻塞等待执行结束 |
gevent.monkey.patch_all() | 猴子补丁,给所有的会导致线程阻塞的方法或函数进行重写 |
Python的线程属于内核级别的,即由操作系统进行系统调度来控制的,如果单线程遇到IO或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行。而使用协程一旦遇到IO阻塞,就需要在代码中手动控制切换(而非操作系统,让渡),以此来提升效率。
我们怎么识别或检测到任务代码中的IO阻塞并自动切换调度到其他任务呢?
1 import time 2 import gevent 3 4 def func(): 5 # 获取当前协程对象 6 print("协程运行了!") 7 8 if __name__ == '__main__': 9 # 创建Greenlet协程对象 10 g1 = gevent.spawn(func) 11 # 阻塞3秒 12 gevent.sleep(3) # gevent的任务调度,需要自动检测到IO阻塞才会切换任务.因为这个IO阻塞的出现,所以执行到协程g1 13 # 相当于time.sleep(1),但是time.sleep无法被协程识别, 14 # 因为协程只能识别属于的异步的阻塞,而time.sleep属于一种同步的阻塞,所以无法切换调度到其他任务 15 # gevent 没有真正的阻塞,属于异步阻塞。time.sleep真正阻塞,属于同步阻塞gevent实现协程调度
1 import gevent 2 3 4 def func1(): 5 print("1-1. func1任务执行了") 6 gevent.sleep(2) 7 print("1-2. func1任务结束了") 8 9 def func2(): 10 print("2-1. func1任务执行了") 11 gevent.sleep(1) 12 print("2-2. func1任务结束了") 13 14 if __name__ == '__main__': 15 # 创建了2个greenlet协程 16 g1 = gevent.spawn(func1) 17 g2 = gevent.spawn(func2) 18 gevent.sleep(3) 19 20 ''' 21 1-1. func1任务执行了 22 2-1. func1任务执行了 23 2-2. func1任务结束了 24 1-2. func1任务结束了 25 ''' 26 """ 27 gevent的内部原理: 28 29 gevent内部实现了libev事件循环(可以简单为死循环,), 30 我们调用gevent的spawn创建greenlet协程对象就是添加了一个协程对象到事件循环内部,类似如下: 31 while True: 32 greenlet.func1() 33 greenlet.func2() 34 35 当程序代码运行时,也就是循环过程中遇到了gevent.sleep(3), 实际上,就是记录了当前调用gevent.sleep(3)的当前时间戳和阻塞等待时间戳而已。 36 假设在主程序中,先调用gevent.sleep(3),实际上就是在协程内部,使用time.time(),记录了当前时间戳(假设是x秒,), 37 还根据当前时间戳+阻塞的时间(此处假设3秒)得到阻塞等待时间戳, 38 那么当前线程中就会有一个列表(调度时间表):[(协程ID,x, x+3)], 39 接着就去切换到事件循环中下一个协程func1,如果协程有gevent.sleep(2),则进行再次使用time.time()记录当前时间戳,并记录x+2, 40 那么当前线程中的调度时间表变成:[(协程ID,x, x+3), (协程ID, x, x+2), ], 41 接着往下调度到另一个任务func2,执行func2的协程中如果再次遇到gevent.sleep(1),那么会再次使用time.time()记录当前时间戳,并记录x+1, 42 那么当前线程中的调度时间表变成:[(x+3, x, 协程ID为), (x+2,x, 协程func1), (x+1,x, 协程func2ID), ], 43 如果当前线程没有其他的协程了,那么调度时间表中使用min函数取出最小时间戳对应信息出来 44 min([(x+3, x, 协程ID为), (x+2,x, 协程func1), (x+1,x, 协程func2ID), ]),提取到(x+1, x, 协程func2) 45 判断时间是否到了,没到就阻塞等待,到了就直接执行对应的该时间戳的协程对应的代码func2 46 执行func2协程的过程中,如果没有再次遇到gevent.sleep的话,则协程直接执行结束, 47 主程序会再次从调度时间表使用min函数取出最小时间戳对应信息出来 48 min([(x+3, x, 协程ID为), (x+2,x, 协程func1)]),提取到(x+2, x, 协程func1) 49 再次等待1秒,时间到,执行对应的func1协程,协程执行如果没有再次遇到阻塞, 50 则再次从调度时间表使用min取出最小时间戳对应信息出来 51 min([(x+3, x, 协程ID为)]),提取到(x+3, x, 协程func1) 52 再次等待1秒,时间到,执行主程序了。 53 """多任务遇到IO阻塞自动切换协程-gevent模块
1 import time 2 3 import gevent 4 5 6 def func1(): 7 print("1-1. func1任务执行了") 8 gevent.sleep(2) 9 print("1-2. func1任务结束了") 10 11 def func2(): 12 print("2-1. func1任务执行了") 13 gevent.sleep(2) 14 print("2-2. func1任务结束了") 15 16 if __name__ == '__main__': 17 task_list = [ 18 gevent.spawn(func1), 19 gevent.spawn(func2) 20 ] 21 # g1.join() 22 # g2.join() 23 gevent.joinall(task_list) 24 ''' 25 1-1. func1任务执行了 26 2-1. func1任务执行了 27 1-2. func1任务结束了 28 2-2. func1任务结束了 29 '''多任务遇到IO阻塞自动切换协程-joinall方法
1 import random 2 import time 3 import gevent 4 5 6 def func(n): 7 print(f"{n}-1. func{n}任务执行了") 8 gevent.sleep(random.random()) 9 print(f"{n}-2. func{n}任务结束了") 10 return f"func{n}的结果" 11 12 def callback(g): 13 """ 14 协程任务的回调处理 15 :param g: 当前协程对象 16 :return: 17 """ 18 print(g.value) 19 20 if __name__ == '__main__': 21 task_list = [] 22 for i in range(10): 23 # g greenlet协程对象 24 g = gevent.spawn(func, i) 25 # 给协程对象注册结果回调处理函数 26 g.link_value(callback) 27 # g.rawlink(callback) 28 task_list.append(g) 29 30 gevent.joinall(task_list)协程任务异步回调处理-gevent
猴子补丁【了解】
在很多的动态语言中,不改变源代码而对功能进行追加和变更的作用,都统称为猴子补丁。猴子补丁就是在模块运行的时候替换或重写模块中的某些方法或函数。
我们需要使用由gevent提供的猴子补丁(monkey-patch)来对python常见的一些导致线程阻塞的函数或方法进行替换
1 import time 2 import gevent 3 print(time.sleep) # <built-in function sleep> 4 # 在导包以后,在程序执行之前,给所有的会导致线程阻塞的方法或函数进行重写 5 from gevent import monkey 6 monkey.patch_all() 7 print(time.sleep) # <function sleep at 0x000001E561584B80> 8 9 def func(): 10 # 获取当前协程对象 11 print("协程func开始运行了!") 12 time.sleep(3) 13 print("协程func运行结束了!") 14 15 if __name__ == '__main__': 16 # 创建Greenlet协程对象 17 g1 = gevent.spawn(func) 18 # time.sleep(1) # 如果使用time则会导致当前阻塞会线程接管,而协程无法识别,也无法干扰 19 # python里面除了time.sleep以外,还有很多会导致线程阻塞的函数或方法,这些函数与方法都无法被协程识别或干扰 20 # 所以,我们需要使用由gevent提供的猴子补丁(monkey-patch)来对python常见的一些导致线程阻塞的函数或方法进行替换 21 time.sleep(3)猴子补丁
协程池【了解】
协程和进程线程一样也有池(pool)的概念的,只是用于限制的并发数量,减轻系统对协程的创建与销毁的资源消耗(协程就是代码对象,所以能够减轻的程度是非常有效的)。
1 import gevent 2 from gevent import pool 3 4 def func1(): 5 print("1-1, func1开始执行了") 6 gevent.sleep(2) 7 print("1-2, func1执行结束了") 8 9 def func2(): 10 print("2-1,func2开始执行了") 11 gevent.sleep(2) 12 print("2-2,func2执行结束了") 13 14 if __name__ == '__main__': 15 # 协程池 16 p = pool.Pool() 17 p.apply_async(func1) 18 p.apply_async(func2) 19 p.join() 20 ''' 21 1-1, func1开始执行了 22 2-1,func2开始执行了 23 1-2, func1执行结束了 24 2-2,func2执行结束了 25 '''协程池
通过上面的代码,我们可以看到协程可以通过单线程内在多个上下文中进行来回切换执行,也可以看到协程在IO密集型操作中,可以利用在IO等待时间就去切换执行其他任务,当IO操作结束后再自动回调,那么就会大大节省资源并提升性能,从而实现异步编程也就是不等待任务结束就可以去执行其他代码。
当然,也要注意的是,协程在计算密集型操作中,如果利用协程来回频繁切换执行,实际上是没有任何意义,因为来回切换并保存代码执行状态反倒会导致程序降低性能。
因此对比操作系统控制线程的上下文切换,用户在单线程内控制协程的上下文切换会带来以下的优缺点和特点:
优点 |
|
缺点 |
|
特点 |
|
五 asyncio模块实现协程调度
python3.4之前使用的都是gevent、eventlet、ternardo、twisted实现协程操作。
asyncio的编程模型就是一个事件循环。我们可以从asyncio模块中直接获取一个EventLoop事件循环的引用对象,然后把需要执行的协程任务注册到EventLoop事件循环中执行,就实现了异步协程了。
事件循环,可以把他当做是一个while循环,这个while循环在周期性的运行并执行一些任务,在特定条件下终止循环
# 伪代码 while True: |
async & awiat
官方推荐使用async & awiat 关键字实现协程异步编程。await是一个只能在协程函数(使用 async 关键字标记的函数)中使用的关键字,用于遇到IO操作时挂起当前协程(任务),当前协程(任务)挂起过程中事件循环就可以自动切换去执行其他的协程(任务),当前协程IO处理挂起状态结束以后,会自动再次切换回来执行await之后的代码。
1 import asyncio 2 3 4 async def func(): 5 print("执行协程函数内部代码") 6 # 遇到IO操作挂起当前协程(任务),等IO操作完成之后再继续往下执行。 7 # 当前协程挂起时,事件循环可以去执行其他协程(任务)。 8 response = await asyncio.sleep(2) 9 print("IO请求结束,结果为:", response) #None 10 11 if __name__ == '__main__': 12 result = func() # 返回一个协程对象 13 asyncio.run(result) # 不能直接调用异步函数,需要使用asyncio模块来运行,否则警告asyncya与wait
asyncio提供的常用方法
方法 | 描述 |
---|---|
await asyncio.sleep(delay, result) |
异步阻塞指定之间,delay参数的值为异步阻塞时间,result的值为阻塞时间结束以后的返回结果 |
loop=asyncio.get_event_loop() | 获得一个事件循环实例对象。 |
await asyncio.wait(fs) |
并发地运行 fs 可迭代对象中的 可等待对象 并进入阻塞状态 |
asyncio.ensure_future(coro) | 创建Task异步任务对象,coro为异步函数 |
loop.run_until_complete(future) | 阻塞运行一个或多个异步任务future,future是异步函数返回的可等待对象 |
asyncio.create_task(coro) | 创建Task异步任务对象,coro为异步函数 |
asyncio.run(main) | 创建事件循环,运行一个协程,协程执行结束以后关闭事件循环。 |
asyncio.as_completed(fs) | 从执行结束的异步任务队列中返回异步任务结果的迭代器 |
task.result() | 获取异步任务的返回结果,task为Task异步任务对象 |
task.add_done_callback(fn) | 设置异步任务的返回结果的异步回调函数,fn为函数名 |
1 import asyncio 2 3 # DeprecationWarning: "@coroutine" decorator is deprecated since Python 3.8, use "async def" instead 4 # 废弃警告:"@coroutine"装饰器,从python3.8版本中已经淘汰了,使用 "async def"替代 5 @asyncio.coroutine 6 def func1(): 7 print("1-1. func1任务执行了") 8 yield from asyncio.sleep(2) # 遇到IO耗时操作,自动化切换到tasks中的其他任务 9 print("1-2. func1任务结束了") 10 11 12 13 if __name__ == '__main__': 14 """方式1:""" 15 # # 1. 创建一个事件循环 16 # loop = asyncio.get_event_loop() 17 # # 2. 基于loop提供的run_until_complete就可以注册生成器对象到事件循环中,自动运行 18 # loop.run_until_complete(func1()) 19 20 """方式2:Python 3.7以后才能使用""" 21 # 本质上方式一是一样的,内部先 创建事件循环 然后执行 run_until_complete,一个简便的写法。 22 # asyncio.run 函数在 Python 3.7 中加入 asyncio 模块, 23 asyncio.run(func1()) 24 25 ''' 26 DeprecationWarning: "@coroutine" decorator is deprecated since Python 3.8, use "async def" instead 27 def func1(): 28 1-1. func1任务执行了 29 1-2. func1任务结束了 30 '''asyncio的基本使用
1 import asyncio 2 3 """ 4 def func(): # 同步函数 5 pass 6 """ 7 8 """定义一个异步函数(协程函数)""" 9 async def func(): 10 print("执行协程函数func开始执行了") 11 # 遇到IO操作挂起当前协程(任务),等IO操作完成之后再继续往下执行。 12 # 当前协程挂起时,事件循环可以去执行其他协程(任务)。 13 response = await asyncio.sleep(2) 14 print("IO请求结束,结果为:", response) 15 16 if __name__ == '__main__': 17 asyncio.run(func()) 18 19 ''' 20 执行协程函数func开始执行了 21 IO请求结束,结果为: None 22 '''异步协程的基本语法
1 import asyncio 2 3 async def func1(): 4 print("协程函数func1开始执行了") 5 await asyncio.sleep(2) 6 return "func1的执行结果" 7 8 async def func2(): 9 print("协程函数func2开始执行了") 10 # 遇到IO操作挂起当前协程(任务),等IO操作完成之后再继续往下执行。 11 # 当前协程挂起时,事件循环可以去执行其他协程(任务)。 12 response = await func1() 13 print("IO请求结束,结果为:", response) 14 15 if __name__ == '__main__': 16 # 不能直接调用异步函数,需要使用asyncio模块来运行,否则警告 17 # print(func2()) # <coroutine object func2 at 0x00000172CC3B11C0> 18 asyncio.run(func2()) 19 ''' 20 协程函数func2开始执行了 21 协程函数func1开始执行了 22 IO请求结束,结果为: func1的执行结果 23 '''异步协程的阻塞切换
1 python3.7以前版本 2 import asyncio 3 4 async def func1(): 5 print("1-1. func1任务执行了") 6 await asyncio.sleep(2) # 遇到IO耗时操作,自动化切换到tasks中的其他任务 7 print("1-2. func1任务结束了") 8 9 10 async def func2(): 11 print("2-1. func2任务执行了") 12 await asyncio.sleep(2) # 遇到IO耗时操作,自动化切换到tasks中的其他任务 13 print("2-2. func2任务结束了") 14 15 if __name__ == '__main__': 16 # python3.7以前的asyncio是沒有run方法的,就要自己手动获取事件循环对象 17 # 注意:此处并非创建一个事件循环对象,这个事件循环对象在python中内部已经创建了 18 loop = asyncio.get_event_loop() 19 20 # # 注册协程对象,返回task异步任务对象 21 # task1 = asyncio.ensure_future(func1()) 22 # task2 = asyncio.ensure_future(func2()) 23 # 24 # # 把task对象添加到协程的就绪(等待)列表 25 # task_list = asyncio.wait([task1,task2]) 26 # 27 # # 把就需要列表中的所有task异步任务对象添加到事件循环中执行 28 # loop.run_until_complete(task_list) 29 30 """简写操作""" 31 task_list = asyncio.wait([ 32 asyncio.ensure_future(func1()), 33 asyncio.ensure_future(func2()) 34 ]) 35 loop.run_until_complete(task_list) 36 37 ''' 38 1-1. func1任务执行了 39 2-1. func2任务执行了 40 1-2. func1任务结束了 41 2-2. func2任务结束了 42 ''' 43 44 45 46 python3.7以后版本 47 48 import asyncio 49 50 async def func1(): 51 print("1-1. func1任务执行了") 52 await asyncio.sleep(2) # 遇到IO耗时操作,自动化切换到tasks中的其他任务 53 print("1-2. func1任务结束了") 54 55 56 async def func2(): 57 print("2-1. func2任务执行了") 58 await asyncio.sleep(2) # 遇到IO耗时操作,自动化切换到tasks中的其他任务 59 print("2-2. func2任务结束了") 60 61 async def main(): 62 print("main子协程开始执行") 63 64 task_list = [ 65 # 创建协程,将协程封装到一个Task对象中。 66 asyncio.create_task(func1(), name="f1"), 67 asyncio.create_task(func2(), name="f2"), 68 ] 69 70 # 添加到事件循环的任务列表中,等待事件循环去执行(默认是就绪状态) 71 await asyncio.wait(task_list) 72 print("main子协程执行结束") 73 74 if __name__ == '__main__': 75 asyncio.run(main())多协程调度python3.7之前之后版本的两种写法
1 python3.7之前 2 3 import asyncio 4 5 async def func(): 6 print("func任务执行了!") 7 await asyncio.sleep(2) 8 print("func任务结束了!") 9 return 'func的执行结果' 10 11 if __name__ == '__main__': 12 loop = asyncio.get_event_loop() 13 task = loop.create_task(func()) 14 loop.run_until_complete(task) 15 ret = task.result() 16 print(f"函数的返回结果:{ret}") 17 18 19 20 python3.7之后 21 22 import asyncio 23 24 25 async def func(): 26 print("func任务执行了!") 27 await asyncio.sleep(2) 28 print("func任务结束了!") 29 return 'func' 30 31 if __name__ == '__main__': 32 task = asyncio.run(func()) 33 print(f"函数的返回结果:{task}") 34 35 ''' 36 func任务执行了! 37 func任务结束了! 38 函数的返回结果:func 39 '''获取异步任务函数的返回结果python3.7之前之后版本的两种写法
1 import asyncio 2 3 4 async def func(): 5 print("func任务执行了!") 6 await asyncio.sleep(2) 7 print("func任务结束了!") 8 return 'func' 9 10 async def main(): 11 print("main开始") 12 task = asyncio.create_task(func()) 13 print("main结束") 14 # 当执行某协程遇到IO操作时,会自动化切换执行其他任务。 15 # 此处的await是等待相对应的协程全都执行完毕并获取结果 16 ret1 = await task 17 print(f"此处获取的ret1就是异步返回结果,ret1={ret1}") 18 19 if __name__ == '__main__': 20 asyncio.run(main())获取异步任务函数的返回结果python3.7之后版本的简写操作
1 python3.7之前的版本 2 import asyncio 3 4 async def func1(): 5 print("1-1. func1任务执行了") 6 await asyncio.sleep(2) # 遇到IO耗时操作,自动化切换到tasks中的其他任务 7 print("1-2. func1任务结束了") 8 return "func1" 9 10 11 async def func2(): 12 print("2-1. func1任务执行了") 13 await asyncio.sleep(2) # 遇到IO耗时操作,自动化切换到tasks中的其他任务 14 print("2-2. func1任务结束了") 15 return "func2" 16 17 if __name__ == '__main__': 18 # 创建一个事件循环对象 19 loop = asyncio.get_event_loop() 20 21 task_list = [ 22 # 注册协程对象,返回task异步任务对象 23 loop.create_task(func1()), 24 loop.create_task(func2()), 25 ] 26 # 并发地运行 task_list 可迭代对象中的 可等待对象 并进入阻塞状态 27 wait_tasks = asyncio.wait(task_list) 28 # 列表中的一个/多个task异步任务对象添加到事件循环中执行 29 loop.run_until_complete(wait_tasks) 30 31 for task in task_list: 32 # 获取异步任务的返回结果,task为Task异步任务对象 33 print(task.result()) 34 ''' 35 1-1. func1任务执行了 36 2-1. func1任务执行了 37 1-2. func1任务结束了 38 2-2. func1任务结束了 39 func1 40 func2 41 ''' 42 43 44 python3.7之后的版本 45 import asyncio 46 47 async def func1(): 48 print("1-1. func1任务执行了") 49 await asyncio.sleep(2) # 遇到IO耗时操作,自动化切换到tasks中的其他任务 50 print("1-2. func1任务结束了") 51 return "func1" 52 53 54 async def func2(): 55 print("2-1. func1任务执行了") 56 await asyncio.sleep(2) # 遇到IO耗时操作,自动化切换到tasks中的其他任务 57 print("2-2. func1任务结束了") 58 return "func2" 59 60 async def main(): 61 task_list = [ 62 # 创建Task异步任务对象 63 asyncio.create_task(func1()), 64 asyncio.create_task(func2()), 65 ] 66 # 并发地运行 task_list 可迭代对象中的 可等待对象 并进入阻塞状态 67 await asyncio.wait(task_list) 68 69 for task in task_list: 70 ret = await task 71 print(f"异步任务的返回结果:{ret}") 72 73 74 if __name__ == '__main__': 75 asyncio.run(main())获取多个异步任务的返回结果python3.7之前之后版本的两种写法
1 python3.7之前版本1 2 import random 3 import asyncio 4 5 6 async def func(i): 7 print(f"异步任务func{i}任务执行了") 8 t = random.randint(1, 10) 9 await asyncio.sleep(t) # 遇到IO耗时操作,自动化切换到tasks中的其他任务 10 print(f"异步任务func{i}任务结束了") 11 return f"func{i}执行结果,耗时:{t}秒" # 此处的返回值,最终在python底层会被分装一个可等待对象 12 13 async def main(): 14 task_list = [] 15 # 注冊创建10个协程 16 for i in range(10): 17 task = asyncio.ensure_future(func(i)) 18 task_list.append(task) 19 20 for res in asyncio.as_completed(task_list): # 从执行结束的任务队列中提取任务对象 21 print(res) # 等待对象,实际上是asyncio.Future,叫可等待对象,可等待对象就可以使用await关键字提取结构 22 result = await res # 因此await后面必须是asyncio封装的可等待对象 23 print(result) 24 25 if __name__ == '__main__': 26 # 创建一个事件循环对象 27 loop = asyncio.get_event_loop() 28 # 列表中的一个/多个task异步任务对象添加到事件循环中执行 29 loop.run_until_complete(main()) 30 31 32 python3.7之前版本2 33 import random 34 import asyncio 35 36 37 async def func(i): 38 print(f"异步任务func{i}任务执行了") 39 t = random.randint(1, 10) 40 await asyncio.sleep(t) # 遇到IO耗时操作,自动化切换到tasks中的其他任务 41 print(f"异步任务func{i}任务结束了") 42 return f"func{i}执行结果,耗时:{t}秒" # 此处的返回值,最终在python底层会被分装一个可等待对象 43 44 45 def callback(res): 46 print(res.result()) 47 48 async def main(): 49 task_list = [] 50 # 注冊创建10个协程 51 for i in range(10): 52 task = asyncio.ensure_future(func(i)) 53 task.add_done_callback(callback) 54 task_list.append(task) 55 56 await asyncio.wait(task_list) 57 58 if __name__ == '__main__': 59 loop = asyncio.get_event_loop() 60 loop.run_until_complete(main()) 61 62 63 64 python3.7之后版本 65 import random 66 import asyncio 67 68 69 async def func(i): 70 print(f"异步任务func{i}任务执行了") 71 t = random.randint(1, 10) 72 await asyncio.sleep(t) # 遇到IO耗时操作,自动化切换到tasks中的其他任务 73 print(f"异步任务func{i}任务结束了") 74 return f"func{i}执行结果,耗时:{t}秒" # 此处的返回值,最终在python底层会被分装一个可等待对象 75 76 77 def callback(res): 78 print(res.result()) 79 80 async def main(): 81 task_list = [] 82 # 注冊创建10个协程 83 for i in range(10): 84 task = asyncio.create_task(func(i)) 85 task.add_done_callback(callback) 86 task_list.append(task) 87 88 await asyncio.wait(task_list) 89 90 if __name__ == '__main__': 91 asyncio.run(main())多协程任务的结果进行异步回调python3.7之前之后版本
uvloop是一个第三方模块,专门用于替代asyncio内置的事件循环loop的。替代了以后,可以让asyncio得到性能的提高,理论上使用uvloop以后的asyncio比原来没有替代前,提升2倍的执行效率,性能可以追上go的协程性能。
1 import random 2 import asyncio 3 4 import uvloop 5 asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) 6 7 async def func(): 8 print(f"异步任务func任务执行了") 9 await asyncio.sleep(3) # 遇到IO耗时操作,自动化切换到tasks中的其他任务 10 print(f"异步任务func任务结束了") 11 return f"func执行结果" # 此处的返回值,最终在python底层会被分装一个可等待对象 12 13 if __name__ == '__main__': 14 ret = asyncio.run(func()) 15 print(ret)高性能的事件循环uvloop
异步http网络模块-aiohttp
自从了asyncio以后,python的异步编程就不再局限于进程和线程以及之前采用各种方式途径实现的协程。python开发中基于asyncio发展了非常适用于实现异步编程的各种的模块。
对于我们前面使用的requests这个http网络请求模块,实际上在异步编程里面,因为requests的网络请求会被线程接管,所以针对异步编程下,就有开发者开发了异步编程里面的网络请求模块,其中比较常用的有httpx与aiohttp,httpx的性能比requests快,但是aiohttp要慢,因此在异步编程中,我们常用的就是aiohttp。
aiohttp就是requests模块的异步实现,因此aiohttp的使用与requests的使用非常类似,但是aiohttp必须配合asyncio来进行使用。aiohttp不仅可以发送网络请求,还可以实现异步http web服务器。
标签:__,func1,协程,任务,print,asyncio 来源: https://www.cnblogs.com/up-zm/p/16310477.html