最近在学习python的异步,网上的很多资料 指向“协程”这个东西,在python3.4版本之后用的是await/async关键字。
如果大家读到这篇文章欢迎大家交流,我阅读过不少博客,基本提到 asyncio.sleep()来模拟实际的生产环境,但是在实际项目中我们并不会用到这个。
在探究 协程问题的时候,随便测试了Flask和Tornado框架,因为模拟爬虫吗,需要做个网页接口,测试结果非常奇怪,再访问Tornado服务器的时候没有并发,即使我用了多线程,多协程依然不行,但是Flask可以,之后为了排除框架的影响 使用time.sleep取代requests.get,发现效果和Flask一致,这我不禁开始怀疑Tornaod框架了。
后来通过查资料发现 如果用time.sleep是不对,应该用gen.sleep,因为time.sleep会阻断线程,因为tornado是单线程,肯定被阻塞啊,对于替换time.sleep没有贴代码,大家可以执行尝试,心塞啊。
在学习中我记录下学习的代码,异步在实际的表现,开发环境为3.7版本,asyncio这个库有些语法 3.7以前版本和3.7版本还是不一样的,会报错。
一、构建服务器(Flask,Tornado)
from flask import Flask
import time
app = Flask(__name__)
@app.route('/')
def index():
time.sleep(3)
return "Hello Word!"
if __name__ == "__main__":
app.run(threaded = True,port=8000)
import tornado.ioloop
import tornado.web
import time
class MainHandler(tornado.web.RequestHandler):
def get(self):
time.sleep(3)#模拟网页数据处理需要耗时三秒
self.write("Hello, world")
def make_app():
return tornado.web.Application([
(r"/", MainHandler),
])
if __name__ == "__main__":
app = make_app()
app.listen(9000)
tornado.ioloop.IOLoop.current().start()
二、测试Tornado环境下的线程、协程
1、单进程,单线程,无协程
import requests
import time
import asyncio
import threading
request_times = 100# 请求次数
Tornado_URL = "http://127.0.0.1:9000"
thread_num = 4 # 线程数
# 单进程 单线程 无协程
def request():
for i in range(request_times):
requests.get(Tornado_URL)
if __name__ == '__main__':
start_time = time.time()
request()
end_time = time.time()
print("Flask 单进程,单线程,无协程 执行时间为:.5f%",end_time-start_time)
2、单进程,多线程,无协程
import time
import asyncio
import threading
request_times = 100# 请求次数
Tornado_URL = "http://127.0.0.1:9000"
thread_num = 4 # 线程数
# 单进程 多线程 无协程
# 一共请求100次,采用四线程每个线程需要25次
def request():
for i in range(request_times//thread_num):
requests.get(Tornado_URL)
if __name__ == '__main__':
pools = [threading.Thread(target=request) for i in range(thread_num)]
start_time = time.time()
for t in pools:
t.setDaemon(True)#将线程声明为守护线程
t.start()
for t in pools:
t.join() #阻塞主线程
end_time = time.time()
print("Tornado 单进程,4线程,无协程 执行时间为:.5f%",end_time-start_time)
看到这里你是不是有很多问号,我也一度怀疑是自己的程序写错了,因为按照逻辑来说,多线程肯定比单线程要快的,可这运行时间竟然还超了,因为有线程切换,这个可以理解,但是没有并发就痛苦了。
3、单进程,单线程,多协程
import requests
import time
import asyncio
import threading
request_times = 100# 请求次数
Tornado_URL = "http://127.0.0.1:9000"
thread_num = 4 # 线程数
# 单进程,单线程,多协程,运行100次。我们拆成100个协程
async def request():
future = loop.run_in_executor(None,requests.get,Tornado_URL)
response = await future
if __name__ == '__main__':
start_time = time.time()
# 开启 request_times 个协程
loop = asyncio.get_event_loop()
tasks = [asyncio.ensure_future(request()) for i in range(request_times)]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
end_time = time.time()
print("Tornado 单进程,单线程,多协程 执行时间为:.5f%",end_time-start_time)
看到这个结果我彻底凌乱了,说好的并行呢?要哭了啊,接下来的多线程多协程我都觉得没必要测了。
4、 单进程,多线程,多协程
import requests
import time
import asyncio
import threading
request_times = 100# 请求次数
Tornado_URL = "http://127.0.0.1:9000"
thread_num = 4 # 线程数
# 单进程,多线程,多协程
async def request(loop):
future = loop.run_in_executor(None,requests.get,Tornado_URL)
response = await future
def taskc_request():
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
tasks = [asyncio.ensure_future(request(loop)) for i in range(request_times//thread_num)]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
if __name__ == '__main__':
pools = [threading.Thread(target=taskc_request) for i in range(thread_num)]
start_time = time.time()
for t in pools:
t.setDaemon(True)#将线程声明为守护线程
t.start()
for t in pools:
t.join() #阻塞主线程
end_time = time.time()
print("Tornado 单进程,4线程,25协程 执行时间为:.5f%",end_time-start_time)
结果不出所料,线程,协程都试了,那就试试进程吧
5、双进程,单线程,无协程
import requests
import time
import asyncio
import threading
from multiprocessing import Process
request_times = 100# 请求次数
Tornado_URL = "http://127.0.0.1:9000"
process_num = 2 # 进程数
# 双进程 单线程 无协程
# 一共请求100次,采用双进程每个进程需要50次
def request():
for i in range(request_times//process_num):
requests.get(Tornado_URL)
if __name__ == '__main__':
start_time = time.time()
pools = [Process(target=request) for i in range(process_num)]
for t in pools:
t.daemon=True#开启守护进程
t.start()
for t in pools:
t.join()
end_time = time.time()
print("Tornado 双进程,单线程,无协程 执行时间为:.5f%",end_time-start_time)
疯了疯了 这都是啥 差点以为 死机了。
后来发现 time.sleep会阻塞整个tornado线程,应该使用gen.sleep,来看看速度
三、为了验证 我试了Flask服务器
仅仅将上面的URL中端口改了就行,Tornado是9000设定的是9000端口,Flask设为8000端口
1、单进程,单线程,无协程
PS:上面的提示信息忘记改了,应该是单线程
2、单进程,多线程,无协程
3、单进程,单线程,多协程(100协程)
4、单进程,多线程,多协程
5、双进程,单线程,无协程
十分神奇,访问Flask服务器竟然可以并发,难道是我理解错了?Tornado不是异步框架吗?
四、以time.sleep取代向网络发送请求,排除 tornado和flask框架的不同
取代方式time.sleep 取代 requests.get
1、单进程 单线程 多协程
import requests
import time
import asyncio
import threading
request_times = 100# 请求次数
thread_num = 4 # 线程数
# 单进程,单线程,多协程
async def request(loop):
future = loop.run_in_executor(None,time.sleep,3)
response = await future
if __name__ == '__main__':
start_time = time.time()
loop = asyncio.get_event_loop()
asyncio.set_event_loop(loop)
tasks = [asyncio.ensure_future(request(loop)) for i in range(request_times)]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
end_time = time.time()
print("Time 单进程,单线程,多协程 执行时间为:.5f%",end_time-start_time)
2、单进程 4线程 无协程
import requests
import time
import asyncio
import threading
request_times = 100# 请求次数
thread_num = 4 # 线程数
# 单进程,多线程,无协程
def request():
for i in range(request_times//thread_num):
time.sleep(3)
if __name__ == '__main__':
pools = [threading.Thread(target=request) for i in range(thread_num)]
start_time = time.time()
for t in pools:
t.setDaemon(True)#将线程声明为守护线程
t.start()
t.join() #阻塞主线程
end_time = time.time()
print("Time 单进程,4线程,无协程 执行时间为:.5f%",end_time-start_time)
3、单进程 4线程 25协程
import requests
import time
import asyncio
import threading
request_times = 100# 请求次数
thread_num = 4 # 线程数
# 单进程,单线程,多协程
async def request(loop):
future = loop.run_in_executor(None,time.sleep,3)
response = await future
def taskc_request():
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
tasks = [asyncio.ensure_future(request(loop)) for i in range(request_times//thread_num)]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
if __name__ == '__main__':
pools = [threading.Thread(target=taskc_request) for i in range(thread_num)]
start_time = time.time()
for t in pools:
t.setDaemon(True)#将线程声明为守护线程
t.start()
t.join() #阻塞主线程
end_time = time.time()
print("Time 单进程,4线程,25协程 执行时间为:.5f%",end_time-start_time)
五、总结
协程真香。