由于 Python 中的协程是运行在一条线程中通过消息队列调控的,如果运行的线程堵塞了那么就会造成消息队列阻塞。为了避免这种情况的发生我们需要区分 IO 密集型任务和 CUP 密集型任务,在 IO 密集型任务中,协程发生阻塞后会在消息队列中挂起转而执行其它协程,而如果是 CUP 密集型任务则需要新开辟线程并放入执行以避免阻塞当前线程。
1 """ 2 协程中 IO 密集型任务和 CPU 密集型任务的区别: 3 4 协程碰到阻塞 IO 后会挂起继续执行下一个任务,在 IO 结束后恢复运行。 5 而 CPU 密集型任务会阻塞整条消息队列(因为协程都是在一条线程里运行的),后果就是异步变成了同步。 6 7 IO 密集型任务举例:请求网络(aiohttp)、读写文件(aiofiles)、操作数据库等,需要用到第三方库 8 CPU 密集型任务举例:解析网页、数值计算等。 9 10 碰到 CUP 密集型任务为了防止队列阻塞,需要用到线程池,aio提供了相应的接口,可见官方文档。 11 """ 12 import asyncio 13 import time 14 15 async def work(n): 16 print(f'{n} is working.') # 这里会同时打印三条信息 17 18 await asyncio.sleep(1) # 模拟 IO 19 20 print(f'{n} is awaked') # 这里只会打印单条信息,因为下方是 CUP 密集型任务,会阻塞消息队列 21 22 s = 0 23 for i in range(1000000): # 模拟 CPU 密集型任务 24 s += i 25 26 print(f'{n} is done.') 27 28 return s 29 30 31 async def main(): 32 st = time.time() 33 tasks = [asyncio.create_task(work(i)) for i in range(3)] # 创建 3 个协程 34 35 await asyncio.gather(*tasks) # 并发启动任务并等待任务结束 36 37 # 取消任务 38 for task in tasks: 39 task.cancel() 40 41 await asyncio.gather(*task, return_exceptions=True) 42 43 print('Done {:.2f}'.format(time.time()-st)) 44 45 46 if __name__ == '__main__': 47 asyncio.run(main())