一 基本概念
协程(Coroutine),是一种用户态的轻量级线程,又称微线程,纤程,可以实现单线程下的并发。是一种用户态内进行上下文切换的技术,由用户程序自己控制任务调度的,简而言之,其实就是通过线程可以实现代码块相互切换执行。协程与线程、进程同属于python中实现异步多任务的常用方式。
CPU能识别的最小任务调度单位是线程,而对于CPU来说,协程是不被识别的或不可见的。
从单进程到多进程提高了 CPU 利用率;从进程到线程,降低了上下文切换的开销;而从线程到协程,进一步降低了上下文切换的开销,使得高并发的服务可以使用简单的代码写出来的。
协程实现了一个线程内多个任务交替执行
-
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 '''
四 基于gevent模块实现协程调度
gevent提供的常用方法
方法 | 描述 |
---|---|
gevent.spawn(任务,任务参数) | 创建greenlet协程对象 |
gevent.spawn(任务1,任务参数).link_value(回调处理函数,函数参数) | 给协程对象注册结果回调处理函数 |
gevent.sleep(n) | 异步的阻塞,没有真正阻塞。可以被协程识别。区别于time.sleep同步阻塞,不可被协程识别 |
gevent.joinall | 基于libev事件循环实现多个协程阻塞等待执行结束 |
gevent.monkey.patch_all() | 猴子补丁,给所有的会导致线程阻塞的方法或函数进行重写 |

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真正阻塞,属于同步阻塞

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 """

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 '''

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)
猴子补丁【了解】
在很多的动态语言中,不改变源代码而对功能进行追加和变更的作用,都统称为猴子补丁。

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: |
5.1 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模块来运行,否则警告
5.2 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为函数名 |
5.3 实例

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 '''

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())

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 '''

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())

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())

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())
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)

import asyncio class B(object): """迭代器""" def __iter__(self): return self def __next__(self): raise StopIteration('end') class A(object): """可等待对象的类""" def __await__(self): return B() # B()就是一个迭代器 async def main(): s = await A() # 可等待对象 print(s) if __name__ == '__main__': task = main() loop = asyncio.run(task) # end

实例一 import asyncio async def func(): print("func任务执行了!") await asyncio.sleep(2) print("func任务结束了!") return 'func的执行结果' async def main(): # loop = asyncio.get_event_loop() # task = loop.create_task(func()) # print(func()) # coroutine 协程对象 task = asyncio.create_task(func()) # 这句代码就是上面2句代码的简写 # print(task) # 可以打印得到Task对象 ret1 = await task # ret1 = task.result() # 此处直接获取结果是不行的,因为当前方法执行的时候,当前异步任务并没有被加入就绪队列中 print(f"ret1={ret1}") if __name__ == '__main__': asyncio.run(main()) 实例二 import asyncio import inspect from asyncio.futures import Future from asyncio.tasks import Task async def func(): print("func任务执行了!") await asyncio.sleep(2) print("func任务结束了!") return 'func的执行结果' if __name__ == '__main__': loop = asyncio.get_event_loop() task = loop.create_task(func()) loop.run_until_complete(task) ret = task.result() # 此处获取结果,因此await task 的作用就是等价于这句话,只是await使用过程,语法要求必须把await写在协程函数中 print(f"函数的返回结果:{ret}") # print(inspect.iscoroutine(func())) # True func()的返回值是一个协程对象 print(inspect.isawaitable(task)) # True task是一个可等待对象 print(isinstance(task, Task)) # True 证明了task对象是Task类的实例 print(issubclass(Task, Future)) # True 证明了Task类是Future的子类 print(isinstance(task, Future)) # True ''' 我们点击result查看源码,可以发现,实际上result方法根本不是Task类提供的,而是一个叫Future类提供的, 那么这个Future类是什么呢? 实际上我们编写异步协程中所有的方法的结果处理操作,实际上都是基于Future对象提供的操作来完成的。 Future是一个相对更偏向底层的可等待对象(awaitable object),它提供了异步编程中的最终结果的处理操作, 所以我们一般把Future也叫异步回调结果对象,虽然平时使用的是Task对象,但对于结果的处理本质是基于Future对象来实现的, Task是Futrue的子类。 '''

import asyncio async def func(fut): print("func1任务执行了!") await asyncio.sleep(2) print("func1任务结束了!") fut.set_result("func1执行结果") # return "func1执行结果" # 在task对象的内部,使用Future.set_result进行了结果的设置 # 没有返回值了!!! async def main(): # 获取当前事件循环 loop = asyncio.get_running_loop() # 创建一个任务(Future对象),没绑定任何行为,则这个任务永远不知道什么时候结束。 fut = loop.create_future() # 创建一个任务(Task对象),绑定了func协程任务函数,函数内部在2s之后通过fut.set_result设置返回值。 # 即手动设置future任务的最终结果,那么fut就可以结束了。 await loop.create_task(func(fut)) # 等待 Future对象获取 最终结果,否则一直等下去 data = await fut print(f"{data=}") if __name__ == '__main__': asyncio.run(main()) ''' func1任务执行了! func1任务结束了! data='func1执行结果' ''' ''' Future对象本身与协程任务函数之间不进行绑定,所以想要让事件循环获取Future的结果,则需要手动设置。 而Task对象继承了Future对象,其实就对Future对象进行扩展,他可以实现在对应绑定的函数执行完成之后, 自动执行`set_result`,从而实现自动结束,自动返回结果给await Future对象语句。 '''
在项目以协程式的异步编程开发时,如果要使用一个第三方模块,那么这个模块必须要实现异步才能与协程交替执行,否则要么报错,要么阻塞执行。所以为了提高程序的性能需要把不支持协程式异步编程的第三方模块转换成异步。
asyncio模块中的时间循环对象提供了run_in_exeutor把本身不支持异步的对象转换成future异步对象,基于这个方法可以实现同步任务转换成异步任务的效果

import asyncio import os.path # requests默认是不支持协程异步的,是一个同步网络请求模块 import requests async def get_img(i, url): # 发送网络请求,下载图片 print(f"开始下载{i+1}:", url) # 同步代码,发起网络请求 # response = requests.get(url) # 同步转异步,让程序遇到网络下载图片的IO请求,自动化切换到其他任务 loop = asyncio.get_running_loop() # run_in_executor(None, 同步阻塞函数或方法名, 函数的参数1,,函数的参数2,函数的参数3....) response = await loop.run_in_executor(None, requests.get, url) print(f'第{i+1}张图片下载完成') # 图片保存到本地文件 filename = os.path.basename(url) with open(f"images/{filename}", mode='wb') as f: f.write(response.content) if __name__ == '__main__': url_list = [ 'https://pic.netbian.com/uploads/allimg/220512/011323-16522892039531.jpg', 'https://pic.netbian.com/uploads/allimg/210831/102129-163037648996ad.jpg', 'https://pic.netbian.com/uploads/allimg/210827/235918-1630079958cd73.jpg' ] tasks = [get_img(i, url) for i, url in enumerate(url_list)] loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.wait(tasks)) 代码优化 import asyncio import os.path # requests默认是不支持协程异步的,是一个同步网络请求模块 import requests async def get_img(i, url): # 发送网络请求,下载图片 print(f"开始下载{i+1}:", url) # 同步代码,发起网络请求 # response = requests.get(url) # 同步转异步,让程序遇到网络下载图片的IO请求,自动化切换到其他任务 loop = asyncio.get_running_loop() # run_in_executor(None, 同步阻塞函数或方法名, 函数的参数1,,函数的参数2,函数的参数3....) response = await loop.run_in_executor(None, requests.get, url) await loop.run_in_executor(None, save_image, url, response) print(f'第{i+1}张图片下载完成') def save_image(url, response): # 图片保存到本地文件 filename = os.path.basename(url) with open(f"images/{filename}", mode='wb') as f: f.write(response.content) if __name__ == '__main__': url_list = [ 'https://pic.netbian.com/uploads/allimg/220512/011323-16522892039531.jpg', 'https://pic.netbian.com/uploads/allimg/210831/102129-163037648996ad.jpg', 'https://pic.netbian.com/uploads/allimg/210827/235918-1630079958cd73.jpg' ] tasks = [get_img(i, url) for i, url in enumerate(url_list)] loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.wait(tasks))

import time import asyncio from asyncio.futures import Future as Future1 from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor from concurrent.futures import Future as Future2 def func1(): print("func1任务执行了!") time.sleep(2) print("func1任务结束了!") return "func1执行结果" def func2(): print("func2任务执行了!") time.sleep(2) print("func2任务结束了!") return "func2执行结果" async def main(): loop = asyncio.get_running_loop() """ loop.run_in_executor的第1个参数实际上是池(Pool),如果传递的参数值是None,则默认创建线程池,然后把func1放到线程池中异步执行 也就是说loop.run_in_executor的内部底层代码的执行流程: 第1步:内部会先调用ThreadPoolExecutor的submit方法去线程池中申请一个线程去执行func1函数,并返回一个concurrent.futures.Future对象 第2步:调用asyncio.wrap_future将concurrent.futures.Future对象包装为asyncio.Future对象。 因为concurrent.futures.Future对象没有实现__await__方法,所以不支持await语法,所以需要包装为 asycio.Future对象 才能使用。 """ # fut1 = loop.run_in_executor(None, func1) # fut2 = loop.run_in_executor(None, func2) # print(type(fut1) is Future1) # True # print(type(fut1) is Future2) # False # # result1 = await fut1 # result2 = await fut2 # # print(f"{result1=}") # print(f"{result2=}") # 2. 把同步函数放到线程池中,申请一个线程异步执行函数 with ThreadPoolExecutor() as pool: # 创建一个线程池 fut = loop.run_in_executor(pool, func1) # 把func1放到线程池申请一个线程异步执行,并把结果包装成asyncio的Future对象 print(type(fut)) result = await fut print(f"线程:{result=}") # 3. 把同步函数放到进程池中,申请一个进程异步执行函数 with ProcessPoolExecutor() as pool: fut = loop.run_in_executor(pool, func1) print(fut) result = await fut print(f"进程:{result=}") if __name__ == '__main__': asyncio.run(main())

import asyncio import random import time class Time(object): """ 异步迭代器 """ def __aiter__(self): return self async def __anext__(self): val = await asyncio.sleep(random.random(), time.time()) if not val: raise StopAsyncIteration return val # list本身只是一个可迭代对象的类 # 基于list创建了子类List,只实现了__aiter__方法,所以List类创建出来的对象就是一个异步可迭代对象。 class List(list): """异步可迭代对象""" def __aiter__(self): return Time() async def main(): # 循环异步可迭代对象,可以使用async for进行遍历,则遍历过程中,执行的是 __aiter__方法,__aiter__方法的返回值必须是异步迭代器 # 注意:如果把异步可迭代对象,使用for进行遍历,则遍历过程中,执行的是__iter__方法,__iter__方法的返回值必须是迭代器 async for item in List([1, 2, 3, 3]): print(item) if __name__ == '__main__': asyncio.run(main()) ''' 1653740947.9042292 1653740948.8492758 1653740949.534409 ... '''
异步迭代器:实现了__aiter__()
和 __anext__()
方法的异步可迭代对象。__anext__

import asyncio import random import time class Time(object): """ 异步迭代器 """ def __aiter__(self): return self async def __anext__(self): val = await asyncio.sleep(1, time.time()) if not val: raise StopAsyncIteration return val async def main(): # 同步的迭代器与异步迭代器在提取数据的时候,都是基于同步获取结果的。 async for item in Time(): print(item) if __name__ == '__main__': asyncio.run(main())
异步上下文管理器

import socket import asyncio class Sniffer(object): """网络嗅探器""" def __init__(self, url, port, timeout=3): self.url = url self.port = port self.timeout = timeout self.socket = socket.socket() self.socket.settimeout(timeout) async def connect(self): """嗅探连接远程服务器,查看指定端口是否开启了""" loop = asyncio.get_running_loop() try: await loop.run_in_executor(None, self.socket.connect, (self.url, self.port)) return True # 表示当前端口是开放的 except: return False # 表示当前端口是没有开放 async def __aenter__(self): print(f"开始连接端口:{self.port}") self.result = await self.connect() return self.result async def __aexit__(self, exc_type, exc, tb): """异步关闭远程socket连接""" if self.socket: self.socket.close() print(f"结束连接端口:{self.port}") async def main(): async with Sniffer("127.0.0.1", 22) as f: '''执行__aenter__,with语句结束后自动执行__aexit__''' print(f) if __name__ == '__main__': asyncio.run(main()) ''' 开始连接端口:22 True 结束连接端口:22 '''
对于我们前面使用的requests这个http网络请求模块,实际上在异步编程里面,因为requests的网络请求会被线程识别阻塞,所以针对异步编程下,就有开发者开发了异步编程里面的异步网络请求模块,其中比较常用的有httpx与aiohttp,httpx的性能比requests快,但是aiohttp要慢,因此在异步编程中,我们经常使用的是aiohttp。
aiohttp基于requests模块的异步实现,但是比requests要功能更多,因此aiohttp的部分功能使用时与requests的使用非常类似的,但是aiohttp必须配合asyncio模块来进行使用。aiohttp不仅可以发送网络请求,还可以实现异步http web服务器。

import asyncio import aiohttp """针对Event loop is closed报错的解决方案有3种方案""" # 1. linux或mac X OS系统下 改成uvloop # import uvloop # asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) # 2. windows系统 asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) async def func(): async with aiohttp.ClientSession() as session: async with session.get("https://pic.netbian.com/uploads/allimg/220512/011323-16522892039531.jpg") as response: with open("1.png", "wb") as f: f.write(await response.read()) if __name__ == '__main__': asyncio.run(func()) # 3. 不要使用run方法,改成python3.7以前的写法 # loop = asyncio.get_event_loop() # loop.run_until_complete(func())
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)