1.为什么要使用线程池?
系统启动一个新线程的成本是比较高的,因为它涉及与操作系统的交互。在这种情形下,使用线程池
可以很好地提升性能,尤其是当程序中需要创建大量生存期很短暂的线程时,更应该考虑使用线程池
。
线程池在系统启动时即创建大量空闲的线程,程序只要将一个函数提交给线程池,线程池
就会启动一个空闲的线程来执行它。当该函数执行结束后,该线程并不会死亡,而是再次返回到线程池中变成空闲状态,等待执行下一个函数。
此外,使用线程池可以有效地控制系统中并发线程的数量。当系统中包含有大量的并发线程时,会导致系统性能急剧下降,甚至导致 Python
解释器崩溃,而线程池的最大线程数参数可以控制系统中并发线程的数量不超过此数。
2.线程池和进程池的介绍
线程池的基类是 concurrent.futures
模块中的 Executor
,Executor
提供了两个子类,即 ThreadPoolExecutor
和 ProcessPoolExecutor
,其中 ThreadPoolExecutor
用于创建线程池,而 ProcessPoolExecutor
用于创建进程池。
如果使用线程池/进程池
来管理并发编程,那么只要将相应的 task
函数提交给线程池/进程池,剩下的事情就由线程池/进程池来搞定。
抽象方法 | 区别 |
---|---|
Executor.submit(fn, *args, **kwargs) | 提交的函数是不一样的, 或者执行的过程中可能出现异常, 就要使用到 submit(), 因为使用 map() 在执行过程中如果出现异常会直接抛出错误, 而 submit() 则会分开处理 |
Executor.map(func, *iterables, timeout=None) | 提交任务的函数是一样的,可以使用 map()方法 |
Executor.shutdown(wait=True) | 由于Executor实现了__enter__和__exit__,使得其对象可以使用with语句,可以避免必须显式调用shutdown()方法。使得当任务执行完成之后,自动执行shutdown函数,而无需编写相关释放代码。 |
3.submit()
3.1ThreadPoolExecutor线程池使用submit()方法
from concurrent.futures import ThreadPoolExecutor
import time
# 参数times用来模拟网络请求的时间
def get_html(times):
time.sleep(times)
print("get page {}s finished".format(times))
return times
executor = ThreadPoolExecutor(max_workers=2)
# 通过submit函数提交执行的函数到线程池中,submit函数立即返回,不阻塞
task1 = executor.submit(get_html, (3))
task2 = executor.submit(get_html, (2))
# done方法用于判定某个任务是否完成
print(task1.done())
# cancel方法用于取消某个任务,该任务没有放入线程池中才能取消成功
print(task2.cancel())
time.sleep(4)
print(task1.done())
# result方法可以获取task的执行结果
print(task1.result())
# 执行结果
# False # 表明task1未执行完成
# False # 表明task2取消失败,因为已经放入了线程池中
# get page 2s finished
# get page 3s finished
# True # 由于在get page 3s finished之后才打印,所以此时task1必然完成了
# 3 # 得到task1的任务返回值
ThreadPoolExecutor
构造实例的时候,传入max_workers
参数来设置线程池中最多能同时运行的线程数目。- 使用
submit
函数来提交线程需要执行的任务(函数名和参数)到线程池中,并返回该任务的句柄(类似于文件、画图),注意submit()
不是阻塞的,而是立即返回。 - 通过
submit
函数返回的任务句柄,能够使用done()
方法判断该任务是否结束。上面的例子可以看出,由于任务有2s的延时,在task1提交后立刻判断,task1
还未完成,而在延时4s之后判断,task1就完成了。 - 使用
cancel()
方法可以取消提交的任务,如果任务已经在线程池中运行了,就取消不了。这个例子中,线程池的大小设置为2,任务已经在运行了,所以取消失败。如果改变线程池的大小为1,那么先提交的是task1
,task2
还在排队等候,这是时候就可以成功取消。 - 使用
result()
方法可以获取任务的返回值。查看内部代码,发现这个方法是阻塞的。
3.2 ProcessPoolExecutor进程池使用submit()方法
import time
from concurrent.futures import ProcessPoolExecutor, wait
def fib(n):
if n <= 2:
return 1
return fib(n - 1) + fib(n - 2)
def execute_process():
start = time.time()
numbers = list(range(30,