起因
需要写个爬虫去爬一些数据,于是用python写了个,但由于众所周知的GIL锁问题,python的多线程其实效率并不高,于是准备采用协程的方法去实现,在写demo测试的时候就遇到问题了,使用await去等待requests的响应却是无效的
测试代码
import asyncio
import requests
async def hello1(url):
print('55555555555555555555')
async with requests.get(url) as resp:
print(url, resp.status_code)
print('666666666666')
async def hello(n,url):
print("协程" + str(n) +"启动")
await hello1(url)
print("协程" + str(n) + "结束")
if __name__ == "__main__":
tasks = []
url = 'http://localhost:8080/TBIMPSWEB/drugPrice/query.tran?REQ_MESSAGE={}'
for i in range(0, 3):
tasks.append(hello(i,url))
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
希望实现的效果是:
当hello1()执行requests.get请求时,服务器还没响应就先去执行其他的协程的代码,也就是输出55555555555部分,我给服务器上对应controller加了断点,希望得到的效果是协程启动-》输出5555,服务器未响应,切换协程-》协程启动-》输出55555-》协程启动-》输出55555,服务器点继续就输出响应码以及6666666
实际效果是:
第一条协程执行到请求部分就停下来了,等服务器响应,不会切到其他的协程去执行
第一次改进后:
import asyncio
import time
import requests
@asyncio.coroutine
def hello1(url):
yield print('55555555555555555555')
print('是否执行请求')
resp = requests.get(url)
print('666666666666')
async def hello(n,url):
print("协程" + str(n) +"启动")
await hello1(url)
print("协程" + str(n) + "结束")
if __name__ == "__main__":
tasks = []
url = 'http://localhost:8080/TBIMPSWEB/drugPrice/query.tran?REQ_MESSAGE={}'
for i in range(0, 3):
tasks.append(hello(i,url))
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
输出结果为:
协程0启动
55555555555555555555
协程1启动
55555555555555555555
协程2启动
55555555555555555555
是否执行请求
显然每次执行到hello1()中的yield部分返回了,去切换到其他协程,但是下面的请求部分并未执行,这样的话不就等于三个协程都执行完前面的计算部分,然后再依次执行io部分,而不是第一个协程去执行io部分的同时,切到其他协程去执行计算部分
解决方案:
最后在一位前辈的指点下才知道问题,requests会阻塞asyncio循环,所以会出现收不到服务器响应的情况。可以使用aiohttp替代requests解决这个问题。
但是stack overflow给出了一个使用requests会阻塞asyncio的解决方法:
import asyncio
import requests
async def hello1(url):
print('55555555555555555555')
loop = asyncio.get_event_loop()
await loop.run_in_executor(None, requests.get, url)
print('666666666666')
async def hello(n,url):
print("协程" + str(n) +"启动")
await hello1(url)
print("协程" + str(n) + "结束")
# await hello2(n)
if __name__ == "__main__":
tasks = []
url = 'http://localhost:8080/TBIMPSWEB/drugPrice/query.tran?REQ_MESSAGE={}'
for i in range(0, 100):
tasks.append(hello(i,url))
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
测试了下,符合预期,把协程数量设置成100,虽然看上去都是先启动,全部启动完了再去运行结束,但是实际上,在其他协程的启动过程中,后台服务器是一直在接收请求的,也就是能实现挂起io操作转而执行其他线程的效果
参考链接:原问题
以及stackoverflow官网上关于这种问题的解释:
https://stackoverflow.com/questions/22190403/how-could-i-use-requests-in-asyncio