并发编程(三)Python编程慢的罪魁祸首。全局解释器锁GIL
并发编程(四)如何使用多线程,使用多线程对爬虫程序进行修改及比较
并发编程(七)好用的线程池ThreadPoolExecutor
并发编程(九)使用多进程multiprocessing加速程序运行
并发编程(十二)使用subprocess启动电脑任意程序(听歌、解压缩、自动下载等等)
web服务的架构以及特点
从上图可以看出,我们使用服务器访问网页时,浏览器会将我们的请求发送给比如Flask或者Django搭建的web服务器。web服务器会进行磁盘文件读取、数据库操作、调用远程服务API等操作,然后将它们的结果进行组合再操作并返回给我们,这就是web服务的大概介绍。
总结来说,web服务有以下特点:
-
web服务对响应时间要求非常高,比如要求200ms返回。
-
web服务有大量的依赖IO操作的调用,比如磁盘文件、数据库、远程API。
-
web服务经常需要处理几万人、几百万人的同时请求。
使用线程池ThreadPoolExecutor加速
使用线程池ThreadPoolExecutor的好处:
- 方便的将磁盘文件、数据库、远程API的IO调用并发执行。
- 线程池的数目不会无限创建(导致系统崩溃挂掉),具有防御功能。
示例(用Flask实现web服务并实现加速)
首先,我们先新建一个flask接口,模拟服务器执行调用。
# -*- coding: utf-8 -*-
# @Time : 2021-03-22 13:59:48
# @Author : wlq
# @FileName: flask_thread_pool.py
# @Email :[email protected]
# 导包
import json
import flask
import time
app = flask.Flask(__name__)
# 磁盘文件读取
def read_file():
time.sleep(0.1)
return "file rst"
# 数据库操作
def read_db():
time.sleep(0.2)
return "db rst"
# 远程API调用
def read_api():
time.sleep(0.3)
return "api rst"
@app.route('/')
def index():
rst_file = read_file()
rst_db = read_db()
rst_api = read_api()
rst = json.dumps({
"result_file": rst_file,
"result_db": rst_db,
"result_api": rst_api
})
return rst
if __name__ == '__main__':
app.run()
运行该接口文件后,就可以在本地浏览器进行请求
接下来,我们用python查看一下调用接口所需时间。
可以看出,网页运行时间是三个操作时间加上框架运行的一点时间。主要还是三个操作耗时。接下来,我们将利用进程池对接口进行修改:
# -*- coding: utf-8 -*-
# @Time : 2021-03-22 13:59:48
# @Author : wlq
# @FileName: flask_thread_pool.py
# @Email :[email protected]
import json
import flask
import time
from concurrent.futures import ThreadPoolExecutor
app = flask.Flask(__name__)
# 定义全局变量的线程池
pool = ThreadPoolExecutor()
def read_file():
time.sleep(0.1)
return "file rst"
def read_db():
time.sleep(0.2)
return "db rst"
def read_api():
time.sleep(0.3)
return "api rst"
@app.route('/')
def index():
rst_file = pool.submit(read_file)
rst_db = pool.submit(read_db)
rst_api = pool.submit(read_api)
rst = json.dumps({
"result_file": rst_file.result(),
"result_db": rst_db.result(),
"result_api": rst_api.result()
})
return rst
if __name__ == '__main__':
app.run()
以上就是修改后的代码,首先我们需要定义一个全局的线程池,便于全局使用。接着,我们使用线程池,就是直接修改调用函数和输出值就可以了。
上图是修改后的代码运行时间,可以看出时间缩短为305ms。较上次缩短300ms。这是为什么呢?
因为,修改前,执行三个操作我们设定时间分别是100ms、200ms、300ms累加起来再加框架时间就是605ms。现在我们增加线程池后,三个操作同时进行。所以耗时就是最长的那个操作加框架运行时间,就是300ms+5ms等于305ms。可以看出,如果同时有更多的操作,请求时长也是占用时长最长的操作时间,而不是所有操作加起来的时间。