啥是异步IO?
Python3 异步IO–asyncio
asyncio是Python 3.4版本引入的标准库,直接内置了对异步IO的支持。
asyncio的编程模型就是一个消息循环。我们从asyncio模块中直接获取一个EventLoop的引用,然后把需要执行的协程扔到EventLoop中执行,就实现了异步IO。
上面的不讲人话,下面是翻译
假设一个url支持同时处理1W的请求,处理时间是1秒,我们要请求这个url 1000次。
如果是依次请求,全部处理完需要花费1000秒。
asyncio就不一样了,他可以在一瞬间将1000个请求同时发出,然后等待所有返回结果。
这样完成1000次请求只需要1秒钟
发现的问题
在网上找了很多demo后,效率是提高了,但是又出了新问题,1000个请求发出后,系统并不能保证哪个请求先返回,所以返回的结果是乱序的,在返回结果里无标识的情况下,根本无法确定哪个结果对应哪个请求。
解决方案
我们通过百度识图的API试验下,举个例子,
(百度识图免费的,某些接口并发量是2,刚好测试)
我们测试识别几张名片
(图片在git上)
名片1
名片2
名片3
名片4
直接上代码,改良后的demo
class BaiduOCR(object):
"""
基于百度识别
示例Demo
"""
def __init__(self, sem):
self.__taskList = [] # 存放loop管理的task
self.__sem = sem # 并发数量上限
def ocr_res(self, req_data, url):
"""
返回所有请求的识别结果
:param req_data:
:param url:
:return:
"""
self.mark_res_async(req_data, url)
result = [json.loads(t.result()) for t in self.__taskList]
print("result", result)
def mark_res_async(self, req_data, url):
"""
配置loop管理所有请求
:param req_data:
:param url:
:return:
"""
asyncio.set_event_loop(asyncio.new_event_loop()) # 支持多线程的async
sem = asyncio.Semaphore(self.__sem) # 控制并发数量
for d in req_data:
# 每个task就是一个请求
# 将_asyncio.Task对象实例的引用暂存起来
self.__taskList.append(asyncio.ensure_future(self.req_baidu_api(d, sem, url)))
loop = asyncio.get_event_loop()
print("__taskList2", self.__taskList)
tasks = set(self.__taskList)
loop.run_until_complete(asyncio.wait(tasks)) # 等待所有请求完成
loop.close()
async def req_baidu_api(self, data, sem, url):
headers = {} # 请求头
res = None
try:
timeout = aiohttp.ClientTimeout(total=30)
async with sem:
async with aiohttp.ClientSession(timeout=timeout) as session:
async with session.post(url=url, data=data, headers=headers) as resp:
res = await resp.text()
return res
except Exception as e:
return res
[{'words_result_num': 10, 'words_result': {'PC': [''], 'TEL': ['0572-86888888', '0572-866666', '88866666'], 'TITLE': ['经理'], 'EMAIL': ['88888@163.com'], 'FAX': [''], 'URL': ['www.88866.com'], 'ADDR': ['江苏省苏州市张家港市中港镇小区'], 'COMPANY': ['设计创意有限公司'], 'MOBILE': ['13813813813'], 'NAME': ['']}, 'log_id': 1427092559734222417},
{'words_result_num': 10, 'words_result': {'PC': [''], 'TEL': ['+8675782713411'], 'TITLE': [''], 'EMAIL': [''], 'FAX': [''], 'URL': ['www.ksw1506.com'], 'ADDR': [''], 'COMPANY': [''], 'MOBILE': [''], 'NAME': ['全国']}, 'log_id': 1427092561799509140},
{'words_result_num': 10, 'words_result': {'PC': [''], 'TEL': ['0757-88331625'], 'TITLE': [''], 'EMAIL': ['lj-ifeng@osugroup.com'], 'FAX': [''], 'URL': [''], 'ADDR': ['广东佛山市祖庙路33号百花广场45楼'], 'COMPANY': [''], 'MOBILE': ['13925949103'], 'NAME': ['']}, 'log_id': 1427092565150553584},
{'words_result_num': 10, 'words_result': {'PC': [''], 'TEL': ['8888888'], 'TITLE': [''], 'EMAIL': [''], 'FAX': ['8800000'], 'URL': [''], 'ADDR': ['惠东县华侨大道'], 'COMPANY': ['COMPARY有限公司'], 'MOBILE': ['1511000'], 'NAME': ['']}, 'log_id': 1427092620186761321}]
这就是按顺序输出的结果了
改进的点和原因分析
请求完成前
__taskList bef [<Task pending coro=<BaiduOCR.req_baidu_api() running at C:/Users/Guristas/Desktop/test/OCR/asyncDemo.py:149>>,
<Task pending coro=<BaiduOCR.req_baidu_api() running at C:/Users/Guristas/Desktop/test/OCR/asyncDemo.py:149>>,
<Task pending coro=<BaiduOCR.req_baidu_api() running at C:/Users/Guristas/Desktop/test/OCR/asyncDemo.py:149>>,
<Task pending coro=<BaiduOCR.req_baidu_api() running at C:/Users/Guristas/Desktop/test/OCR/asyncDemo.py:149>>]
请求完成后
__taskList aft [<Task finished coro=<BaiduOCR.req_baidu_api() done, defined at C:/Users/Guristas/Desktop/test/OCR/asyncDemo.py:149> result='{"words_resu...559734222417}'>,
<Task finished coro=<BaiduOCR.req_baidu_api() done, defined at C:/Users/Guristas/Desktop/test/OCR/asyncDemo.py:149> result='{"words_resu...561799509140}'>,
<Task finished coro=<BaiduOCR.req_baidu_api() done, defined at C:/Users/Guristas/Desktop/test/OCR/asyncDemo.py:149> result='{"words_resu...565150553584}'>,
<Task finished coro=<BaiduOCR.req_baidu_api() done, defined at C:/Users/Guristas/Desktop/test/OCR/asyncDemo.py:149> result='{"words_resu...620186761321}'>]
发现了奇怪的东西
Task对象实际上就是一个请求,有pending 和finished两种状态,结束后,我们调用Task的resule方法获取返回结果。
loop也是对Task进行管理,保证所有请求返回结果后,再关闭loop。
而且syncio.wait()的都是Task对象的引用。
那么这里都可以操作一波了
我们提前将Task对象的引用按照自己喜欢的顺序保存下来,保存到一个列表里(self.__taskList),等待所有请求完成后,loop关闭,我们直接通过遍历__taskList获取Task对象,调用result方法得到请求结果,就是我们想要的顺序了。
BTW:__taskList也可以换成dict,键为请求名,值为请求结果,随便怎么搞,只要能保证一一对应就可以。
完整代码在git,需要的的自行拉取
https://github.com/Guristasrabbit/baiduOCR_asyncio_demo.git