一 协程
协程也可以被称为微线程,是一种用户态内的上下文切换技术。简而言之,其实就是 通过一个线程实现代码块相互切换执行。例如:
示例
def func1(): print(1) print(2) def func2(): print(3) print(4) func1() func2()
如果我们执行上面的代码就会执行出来的结果是 1 , 2, 3, 4 但是如果协程接入的话 就出现了 1, 3, 2, 4
协程的实现
from greenlet import greenlet def func1(): print(1) # 第1步:输出 1 gr2.switch() # 第3步:切换到 func2 函数 print(2) # 第6步:输出 2 gr2.switch() # 第7步:切换到 func2 函数,从上一次执行的位置继续向后执⾏ def func2(): print(3) # 第4步:输出 3 gr1.switch() # 第5步:切换到 func1 函数,从上一次执行的位置继续向后执⾏ print(4) # 第8步:输出 4 gr1 = greenlet(func1) gr2 = greenlet(func2) gr1.switch() # 第1步:去执行 func1 函数
yield
基于Python的生成器的yield和yield form关键字实现协程代码。
def func1(): yield 1 yield from func2() yield 2 def func2(): yield 3 yield 4 f1 = func1() for item in f1: print(item)
asyncio
import asyncio @asyncio.coroutine def func1(): print(1) yield from asyncio.sleep(2) # 遇到IO耗时操作,自动化切换到tasks中的其他任务 print(2) @asyncio.coroutine def func2(): print(3) yield from asyncio.sleep(2) # 遇到IO耗时操作,自动化切换到tasks中的其他任务 print(4) tasks = [ asyncio.ensure_future(func1()), asyncio.ensure_future(func2()) ] loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.wait(tasks))
注意:基于asyncio模块实现的协程比之前的要更厉害,因为他的内部还集成了遇到IO耗时操作自动切 花的功能。
async & awit
async & awit 关键字在Python3.5版本中正式引入,基于他编写的协程代码其实就是 上一示例 的加强 版,让代码可以更加简便。 Python3.8之后 @asyncio.coroutine 装饰器就会被移除,推荐使用sync & awit 关键字实现协程代码。
import asyncio async def func1(): print(1) await asyncio.sleep(2) print(2) async def func2(): print(3) await asyncio.sleep(2) print(4) tasks = [ asyncio.ensure_future(func1()), asyncio.ensure_future(func2()) ] loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.wait(tasks))
二 爬虫案例
例如:用代码实现下载 url_list 中的图片。
方式一:同步编程实现
import requests def download_image(url): print("开始下载:", url) # 发送⽹络请求,下载图片 response = requests.get(url) print("下载完成") # 图片保存到本地文件 file_name = url.rsplit('_')[-1] with open(file_name, mode='wb') as file_object: file_object.write(response.content) if __name__ == '__main__': url_list = [ 'https://www3.autoimg.cn/newsdfs/g26/M02/35/A9/120x90_0_autohomecar__ChsEe12AXQ6AOOH_AAFocMs8nzU621.jpg', 'https://www2.autoimg.cn/newsdfs/g30/M01/3C/E2/120x90_0_autohomecar__ChcCSV2BBICAUntfAADjJFd6800429.jpg', 'https://www3.autoimg.cn/newsdfs/g26/M0B/3C/65/120x90_0_autohomecar__ChcCP12BFCmAIO83AAGq7vK0sGY193.jpg' ] for item in url_list: download_image(item)
方式二 异步
""" 下载图片使用第三方模块aiohttp,请提前安装:pip3 install aiohttp """ # !/usr/bin/env python # -*- coding:utf-8 -*- import aiohttp import asyncio async def fetch(session, url): print("发送请求:", url) async with session.get(url, verify_ssl=False) as response: content = await response.content.read() file_name = url.rsplit('_')[-1] with open(file_name, mode='wb') as file_object: file_object.write(content) async def main(): async with aiohttp.ClientSession() as session: url_list = [ 'https://www3.autoimg.cn/newsdfs/g26/M02/35/A9/120x90_0_autohomecar__ChsEe12AXQ6AOOH_AAFocMs8nzU621.jpg', 'https://www2.autoimg.cn/newsdfs/g30/M01/3C/E2/120x90_0_autohomecar__ChcCSV2BBICAUntfAADjJFd6800429.jpg', 'https://www3.autoimg.cn/newsdfs/g26/M0B/3C/65/120x90_0_autohomecar__ChcCP12BFCmAIO83AAGq7vK0sGY193.jpg' ] tasks = [asyncio.create_task(fetch(session, url)) for url in url_list] await asyncio.wait(tasks) if __name__ == '__main__': asyncio.run(main())
三 异步编程
基于 async & await 关键字的协程可以实现异步编程,这也是目前python异步相关的主流技术。 想要真正的了解Python中内置的异步编程,根据下文的顺序一点点来看
3.1 事件循环
事件循环,可以把他当做是一个while循环,这个while循环在周期性的运行并执行一些 任务 ,在特定条 件下终止循环
import asyncio loop = asyncio.get_event_loop()
3.2 协程和异步编程
协程函数,定义形式为 async def 的函数。
协程对象,调用协程函数 所返回的对象。
# 定义一个协程函数 async def func(): pass
# 调用协程函数,返回一个协程对象 result = func()
# 注意:调用协程函数时,函数内部代码不会执行,只是会返回一个协程对象
3.2.1 基本应用
程序中,如果想要执行协程函数的内部代码,需要 事件循环 和 协程对象 配合才能实现,如:
import asyncio async def func(): print("协程内部代码") # 调用协程函数,返回一个协程对象。 result = func() # 方式一 # loop = asyncio.get_event_loop() # 创建⼀个事件循环 # loop.run_until_complete(result) # 将协程当做任务提交到事件循环的任务列表中,协程执行完成之后终止。
# 方式二 # 本质上方式一模一样的,内部先 创建事件循环 然后执行 run_until_complete,一个简便的写法。 # asyncio.run 函数在 Python 3.7 中加入 asyncio 模块, asyncio.run(result)
3.2.2 await
【await+可等待对象(协程对象,Future,Task对象)】
await是一个只能在协程函数中使用的关键字,用于遇到IO操作时挂起 当前协程(任务),当前协程 (任务)挂起过程中 事件循环可以去执行其他的协程(任务),当前协程IO处理完成时,可以再次切换 回来执行await之后的代码。代码如下:
示例1
import asyncio async def func(): print("执⾏协程函数内部代码") # 遇到IO操作挂起当前协程(任务),等IO操作完成之后再继续往下执⾏。 # 当前协程挂起时,事件循环可以去执⾏其他协程(任务)。 response = await asyncio.sleep(2) print("IO请求结束,结果为:", response) result = func() loop = asyncio.get_event_loop() loop.run_until_complete(result) # asyncio.run(result)
示例2
import asyncio async def others(): print("start") await asyncio.sleep(2) print('end') return '返回值' async def func(): print("执行协程函数内部代码") # 遇到IO操作挂起当前协程(任务),等IO操作完成之后再继续往下执行。当前协程挂起时,事件循环可以去执行其他协程(任务)。 response = await others() print("IO请求结束,结果为:", response) loop = asyncio.get_event_loop() loop.run_until_complete(func()) # asyncio.run(func())
示例3
import asyncio async def others(): print("start") await asyncio.sleep(2) print('end') return '返回值' async def func(): print("执行协程函数内部代码") # 遇到IO操作挂起当前协程(任务),等IO操作完成之后再继续往下执行。当前协程挂起时,事件循环可以去执行其他协程(任务)。 response1 = await others() print("IO请求结束,结果为:", response1) response2 = await others() print("IO请求结束,结果为:", response2) # asyncio.run(func()) loop = asyncio.get_event_loop() loop.run_until_complete(func())
上述的所有示例都只是创建了一个任务,即:事件循环的任务列表中只有一个任务,所以在IO等待时 无法演示切换到其他任务效果。 在程序想要创建多个任务对象,需要使用Task对象来实现。
3.2.3 Task对象
Tasks用于并发调度协程,通过 asyncio.create_task(协程对象) 的方式创建Task对象,这样可以让协程加入事件循环中等待被调度执行。除了使用 asyncio.create_task() 函数以外,还可以用低层级 的 loop.create_task() 或 ensure_future() 函数。不建议手动实例化 Task 对象。 本质上是将协程对象封装成task对象,并将协程立即加入事件循环,同时追踪协程的状态。
注意: asyncio.create_task() 函数在 Python 3.7 中被加入。在 Python 3.7 之前,可以改用低层级 的 asyncio.ensure_future() 函数
import asyncio async def func(): print(1) await asyncio.sleep(2) print(2) return "返回值" async def main(): print("main开始") # 创建协程,将协程封装到⼀个Task对象中并立即添加到事件循环的任务列表中,等待事件循环去执行(默认是就绪状态)。 task1 = asyncio.create_task(func()) # 创建协程,将协程封装到⼀个Task对象中并立即添加到事件循环的任务列表中,等待事件循环去执行(默认是就绪状态)。 task2 = asyncio.create_task(func()) print("main结束") # 当执行某协程遇到IO操作时,会自动化切换执行其他任务。 # 此处的await是等待相对应的协程全都执行完毕并获取结果 ret1 = await task1 ret2 = await task2 print(ret1, ret2) asyncio.run(main())