十六.线程池概念
1.什么是线程池
与进程池类似, 线程池是在系统启动时就先创建大量空闲的线程, 程序提交一个任务给线程池, 线程池便会调用一个线程来执行该任务, 当任务运行完毕后, 该线程并不会关闭, 而是返回到线程池中再次变为空闲状态等待下一个提交的任务,
2.为什么使用线程池
虽说线程的启动相比较于进程开销非常小, 但毕竟也是需要向操作系统发起调用, 我们使用线程在一些情况下能更好的提升性能, 尤其是程序中有大量生命期短暂的线程时, 使用线程池最为合适了
3.线程池的作用
使用线程池可以精确控制操作系统中并发线程的数量, 如果操作系统中有大量的并发线程, 并且没有限制数量, 那么就会导致操作系统的性能急剧下降, 甚至导致程序的崩溃, 而线程池可以通过控制最大线程数来解决该问题
十七.线程池的使用 (concurrent.futures 模块)
1.concurrent.futures 模块
concurrent.futures模块是Python标准库模块, 它提供了Executor类, 而 Executor又提供了两个子类,即ThreadPoolExecutor和 ProcessPoolExecutor,其中ThreadPoolExecutor用于创建线程池,而ProcessPoolExecutor用于创建进程池, 也可以把这两个子类看作高度封装的异步调用接口
2.常用方法
方法 | 说明 |
submit(fn, *args, **kwargs) | 将任务fn提交给进程池(异步提交), 后面的是参数 |
map(func, *iterables, timeout=None, chunksize=1) | 类似于map函数, 升级版本开启多进程以异步的方式来对可迭代对象进行map处理,也类似于取代for循环submit的操作 |
shutdown(wait=True) | 关闭线程池, (下面进行详解) |
result(timeout=None) | 获得该线程任务的结果, 如果任务未执行完会进行阻塞, 参数为超时时间 |
add_done_callback(fn) | 为该线程设置回调, 当该线程完成任务时, 程序会自动触发fn函数 |
-
shutdown(wait=True)
详解:
相当于进程池的**pool.close( ) + pool.join( )**操作
wait=True, 等待池内所有任务执行完毕回收完资源后才继续
wait=False, 立即返回, 并不会等待池内的任务执行完毕
但不管wait参数为何值,整个程序都会等到所有任务执行完毕
submit和map必须在shutdown之前
注意 : 在用完一个线程池后,应该调用该线程池的**shutdown( )方法,该方法将启动线程池的关闭序列, 调用shutdown( )**方法后的线程池不再接收新任务, 但会将以前所有的已提交任务执行完成, 当线程池中的所有任务都执行完成后, 该线程池中的所有线程都会死亡
3.使用ThreadPoolExecutor线程池启动线程任务步骤
- 使用ThreadPoolExecutor类创建一个线程池对象
- 创建一个任务task (普通函数)
- 调用线程池对象的**submit( )**方法来提交任务
- 当没有任务的时候调用**shutdown( )**方法来关闭进程池
4.ThreadPoolExecutor使用示例
**ps 😗*ProcessPoolExecutor 与 ThreadPoolExecutor 的使用方法一模一样
from concurrent.futures import ThreadPoolExecutor
import os,time,random
def task(n):
print(f"子线程:{os.getpid()}正在执行")
time.sleep(random.randint(1,3)) # 模拟任务执行时间
return n**2
if __name__ == '__main__':
thread_pool = ThreadPoolExecutor(max_workers=4) # 设置线程池大小
futures = []
for i in range(1,10):
future = thread_pool.submit(task,i) # 开启十个任务
futures.append(future)
thread_pool.shutdown(True) # 关闭线程池,并等待任务结束
for future in futures:
print(future.result()) # 循环取出任务运行的结果(等到左右的任务执行完后才拿到)
'''输出
子线程:1360正在执行
子线程:1360正在执行
子线程:1360正在执行
子线程:1360正在执行
子线程:1360正在执行
子线程:1360正在执行
子线程:1360正在执行
子线程:1360正在执行
子线程:1360正在执行
1
4
9
16
25
36
49
64
81
'''
5.回调函数的使用 (add_done_callback( ))
-
submit( )
提交任务future, 当future调用resule( )
方法, 会阻塞当前主线程, 等到所有线程完成任务后, 该阻塞才会解除, 于是拿到的结果入上面的示例一样, 先运行完十个任务, 再拿到十个结果 - 如果不想让
resule( )
方法将线程阻塞, 那么就可以使用future的add_done_callback( )
来添加回调函数, 当线程任务结束后, 程序会自动触发该回调函数, 并将future对象结果作为参数传给回调函数, 那我们可以在其内直接打印结果
from concurrent.futures import ThreadPoolExecutor
import os,time,random
def task(n):
print(f"子线程:{os.getpid()}正在执行")
time.sleep(random.randint(1,3))
return n**2
def resule_back(res):
print(res.result()) # 打印任务运行的结果(不需要等待其他线程任务完成)
if __name__ == '__main__':
thread_pool = ThreadPoolExecutor(max_workers=4)
for i in range(1,10):
future = thread_pool.submit(task,i)
future.add_done_callback(resule_back) # 设置回调函数
thread_pool.shutdown(True) # 关闭线程池
'''输出
子线程:17164正在执行
子线程:17164正在执行
子线程:17164正在执行
子线程:17164正在执行
4
子线程:17164正在执行
1
子线程:17164正在执行
9
1625
子线程:17164正在执行
子线程:17164正在执行
36
子线程:17164正在执行
8164
49
'''
6.map( ) 方法的使用
-
map( )
方法第一个参数是函数, 第二个是可迭代对象, 第三个是超时时间 - 这种方法相当于是开启
len(iterable)
个线程, 等于是替代了 for + submit( ) 这两步 -
map( )
方法得到的结果是一个生成器(generator)对象, 可以使用list( )
函数造成列表 - 优点 : 代码简单, 最后收集任务运行的结果, 仍然与传入参数的顺序一样
from concurrent.futures import ThreadPoolExecutor
import os,time,random
def task(n):
print(f"子线程:{os.getpid()}正在执行")
time.sleep(random.randint(1,3))
return n**2
if __name__ == '__main__':
thread_pool = ThreadPoolExecutor(max_workers=4)
# 相当于替代了for+submit(),返回的是一个可list的对象
future = thread_pool.map(task,range(1,10))
thread_pool.shutdown(True) # 关闭线程池
print(future)
print(list(future)) # [1, 4, 9, 16, 25, 36, 49, 64, 81]
'''输出
子线程:1096正在执行
子线程:1096正在执行
子线程:1096正在执行
子线程:1096正在执行
子线程:1096正在执行
子线程:1096正在执行
子线程:1096正在执行
子线程:1096正在执行
子线程:1096正在执行
<generator object Executor.map.<locals>.result_iterator at 0x000001EDF4C72748>
[1, 4, 9, 16, 25, 36, 49, 64, 81]
'''
7.使用回调函数爬取各网站大小示例
from concurrent.futures import ThreadPoolExecutor
import requests,os
def get_htm(url):
print(f"线程:{os.getpid()}正在获取网站:{url}源码")
response = requests.get(url)
if response.status_code == 200:
return {"url":url,"text":response.text}
else:
return {"url":url,"text":""}
def parse_htm(back):
res = back.result()
print(f"线程:{os.getpid()}正在解析网站:{url}源码")
with open("html_size.txt","a")as f:
f.write(f"url:{res['url']},size:{len(res['text'])}\n")
if __name__ == '__main__':
urls=[
'https://zhuanlan.zhihu.com',
'',
'https://www.python.org',
'',
'http://www.china.com.cn',
]
li = []
thread_pool = ThreadPoolExecutor(3)
for url in urls:
future = thread_pool.submit(get_htm,url)
future.add_done_callback(parse_htm)
li.append(future)
thread_pool.shutdown(True)
'''输出
线程:6392正在获取网站:https://zhuanlan.zhihu.com源码
线程:6392正在获取网站:https://www.python.org源码
线程:6392正在解析网站:http://www.china.com.cn源码
线程:6392正在解析网站:http://www.china.com.cn源码
线程:6392正在获取网站:http://www.china.com.cn源码
线程:6392正在解析网站:http://www.china.com.cn源码
线程:6392正在解析网站:http://www.china.com.cn源码
线程:6392正在解析网站:http://www.china.com.cn源码
'''
- 查看下文件