python并发编程笔记5
是根据蚂蚁学Python的视频做的笔记,方便自己后续回顾
视频链接:BV1bK411A7tV
老师的源码
这一份笔记对应的是视频的P7-P8
P7-Python好用的线程池ThreadPoolExecutor
1、线程池的原理
1.1、线程的生命周期
新建线程系统需要分配资源、终止线程系统需要回收资源
如果可以重用线程,则可以减去新建/终止的开销
1.2、线程池是怎么流转的呢
由两部分组成
线程池:里面是提前预先建好的线程,线程会被重复使用
任务队列:新的任务而不是直接创建线程
线程池会将任务队列的任务挨个取出依次执行,任务执行完后会取下一个任务执行,若没有任务,则回到线程池,不会进行销毁
2、使用线程池的好处
2.1、提升性能:
因为减去了大量新建、终止线程的开销,重用了线程资源;
2.2、适用场景:
适合处理突发性大量请求或需要大量线程完成任务、但实际任务处理时间较短
2.3、防御功能:
能有效避免系统因为创建线程过多,而导致系统负荷过大相应变慢等问题
2.4、代码优势:
使用线程池的语法比自己新建线程执行线程更加简洁
3、ThreadPoolExecutor的使用语法
前提:导入对应模块
from concurrent.futures import ThreadPoolExecutor,as_completed
用法1:map函数
很简单,注意map结果和入参是顺序对立的(指results 与 urls的顺序是一一对应的)
pool.map(函数,列表)会自动创建和列表等数量(不确定)的线程来处理。
with ThreadPoolExecutor() as pool:
# 传入一个函数名(craw),和参数列表(urls)
results = pool.map(craw, urls)
for result in results:
print(result)
用法2:future模式
更强大,注意如果用as——completed顺序是不定的
with ThreadPoolExecutor() as pool:
# 传入的是单个url,而不是urls,返回的是一个future对象,放进一个futures里面
futures = [ pool.submit(craw, url)
for url in urls ]
# 结果两种遍历方式
# 会根据urls的对应顺序挨个获取future,并且获取结果
for future in futures:
print(future.result())
# as_completed方式,好处:哪个任务先执行完了就先返回
for future in as_completed(futures):
print(future.result())
4、使用线程池改造爬虫程序
注意事项:
视频作者的代码讲的比较快,一些基础我这里普及一下
代码中使用的
zip方法是将urls与对应的的htmls新包为一个元组。
fufturs字典也是为存储跟上述相似的对应关系。
zip实例:
url_list = [1, 2, 3, 4]
html_list = ["a", "b", "c", "d"]
html_list = list(zip(url_list, html_list))
print(html_list)
for url, html in html_list:
print(url, len(html))
结果:
[(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd')]
1 1
2 1
3 1
4 1
代码:
import concurrent.futures
import blog_spider
# craw,使用map方法调用线程池
with concurrent.futures.ThreadPoolExecutor() as pool:
htmls = pool.map(blog_spider.craw, blog_spider.urls)
htmls = list(zip(blog_spider.urls, htmls))
for url, html in htmls:
print(url, len(html))
print("craw over")
# parse 使用future模式调用线程池
with concurrent.futures.ThreadPoolExecutor() as pool:
futures = {}
for url, html in htmls:
future = pool.submit(blog_spider.parse, html)
# 加入到字典的是好处url与future有对应关系
futures[future] = url
# 得到结果 有序的,等所有任务执行完了再返回
# for future, url in futures.items():
# print(url, future.result())
# as_completed方式,好处:哪个任务先执行完了就先返回,相比上面这个是无序的
for future in concurrent.futures.as_completed(futures):
url = futures[future]
print(url, future.result())
总结:
使用这种map或者future模式比02中使用for循环实现thread好用的多
P8-Python使用线程池在Web服务中实现加速
1、Web服务的架构以及特点
Web后台服务的特点:
1、Web服务对响应时间要求非常高,比如要求200MS返回
2、Web服务有大量的依赖IO操作的调用,比如磁盘文件、数据库、远程API(暗示使用线程池来加速)
3、Web服务经常需要处理几万人、几百万人的同时请求(不能无限的创建线程池子)
2、使用线程池ThreadPoolExecutor加速
使用线程池ThreadPoolExecutor的好处:
1、方便的将磁盘文件、数据库、远程API的IO调用并发执行
2、线程池的线程数目不会无限创建(导致系统挂掉),具有防御功能
3、代码用Flask实现Web服务并实现加速
import flask
import json
import time
from concurrent.futures import ThreadPoolExecutor
app = flask.Flask(__name__)
pool = ThreadPoolExecutor()
def read_file():
time.sleep(0.1)
return "file result"
def read_db():
time.sleep(0.2)
return "db result"
def read_api():
time.sleep(0.3)
return "api result"
# @app.route("/")
# def index():
# result_file = read_file()
# result_db = read_db()
# result_api = read_api()
#
# return json.dumps({
# "result_file": result_file,
# "result_db": result_db,
# "result_api": result_api,
# })
@app.route("/")
def index():
result_file = pool.submit(read_file)
result_db = pool.submit(read_db)
result_api = pool.submit(read_api)
return json.dumps({
"result_file": result_file.result(),
"result_db": result_db.result(),
"result_api": result_api.result(),
})
if __name__ == '__main__':
app.run()
总结:
通过线程池加速Web服务
全局中创建一个pool对象,用submit获取file对象,用result获取结果