![v2-19d24858c15404e30518e561e409fb23_1440w.jpg?source=172ae18b](http://img-03.proxy.5ce.com/view/image?&type=2&guid=ff1526ee-cd2f-eb11-8da9-e4434bdf6706&url=https://pic3.zhimg.com/v2-19d24858c15404e30518e561e409fb23_1440w.jpg?source=172ae18b)
Python 3.2版本之后发布了
concurrent.futures
模块,用以支持和管理并发编程,内容涵盖了进程和线程池(Thread and Process Pooling)、非确定性执行流(Nondeterministic Execution Flows)以及进程和线程同步。
![v2-aa6a0822ee5d239077f53954909b9db1_b.jpg](http://img-02.proxy.5ce.com/view/image?&type=2&guid=ff1526ee-cd2f-eb11-8da9-e4434bdf6706&url=https://pic2.zhimg.com/v2-aa6a0822ee5d239077f53954909b9db1_b.jpg)
本文通过将带有可选参数的任务提交(Submit)给执行器(Executor)来实例化futures
对象。执行器是线程或者进程执行池访问的父类,线程或者进程实例的使用比较耗费资源,因此需要尽可能的重复利用资源,提升整体性能。为此Python提供了concurrent.futures
模块实现池(Pooling)的概念,并提供了如下的类:
concurrent.futures.Executor
- 用于异步执行调用;Submit(function, argument)
- 调度一个函数的执行map(function, argument)
- 异步方式执行函数shutdown(Wait = True)
- 通知执行器(Executor)释放资源concurrent.futures.Future
- 封装了回调函数的异步执行
如何处理线程或者进程池
线程或进程池(也称为池)表示用于优化和简化程序中线程和/或进程资源管理的软件管理器。通过池化,可以向池中提交要执行的任务。池配备了一个挂起任务的内部队列,以及执行这些任务的多个线程或进程。池中一个重要的概念是重用。线程或进程在其生命周期中多次用于不同的任务。它减少了创建和提高利用池的程序性能的开销。
Executor的子类
重用是致程序员使用池的主要原因之一。concurrent.futures
模块提供了executor
类的两个子类,它们分别异步操作线程池和进程池。这两个子类如下所示:
- concurrent.futures.ThreadPoolExecutor(max_workers)
- concurrent.futures.ProcessPoolExecutor(max_workers)
其中,max_workers参数标识异步执行调用的最大工作线程数。
顺序 vs. 线程/进程池
下面这个例子我们展示了进程和线程池的使用,代码要执行的任务是我们有一个从1到10的数字列表,数字列表。对于列表中的每个元素,一个计数器统计1000万次,目的只是浪费时间。
- 顺序执行
- 5个worker的线程池执行
- 5个worker的进程池执行
我们建立一个存储在number_list中的数字列表,对于列表中的每个元素,我们操作计数过程直到1亿次迭代。然后我们将得到的值乘以1亿。在主程序中,我们执行将以顺序模式执行的任务;在并行模式下,我们将对线程池使用concurrent.futures模块的池功能。线程池执行器使用其内部池线程之一执行给定任务。它管理在其池中工作的文件线程。每个线程从池中取出一个作业并执行它。执行作业时,它将从线程池中接受下一个要处理的作业。处理完所有作业后,将打印执行时间;与threadpoolexecutor类似,processpoolexecutor类是一个executor子类,它使用进程池异步执行调用。然而,与threadpoolexecutor不同,进程池执行器使用multiprocessing模块,这可以跳出全局解释器锁的限制,获得更短的执行时间。
""" Asynchronous Programming """
import concurrent.futures
import time
number_list = [x+1 for x in range(10)]
def count(number):
for i in range(10_000_000):
i += 1
return i * number
def evaluate_item(x):
result_item = count(x)
print('item ' + str(x) + ' result ' + str(result_item))
if __name__ == "__main__":
# Sequential Execution
start_time = time.clock()
for n in number_list:
evaluate_item(n)
print('Sequential execution in ' +
str(time.clock() - start_time), 'seconds')
# Thread pool Execution
start_time = time.clock()
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
for n in number_list:
executor.submit(evaluate_item, n)
print('Thread pool execution in ' +
str(time.clock() - start_time), 'seconds')
# Process pool Execution
start_time = time.clock()
with concurrent.futures.ProcessPoolExecutor(max_workers=5) as executor:
for n in number_list:
executor.submit(evaluate_item, n)
print('Process pool execution in ' +
str(time.clock() - start_time), 'seconds')
现在我们打开命令提示符运行python文件,最终我们会得到下面的结果。由于多线程收到GIL的限制,因为是计算密集型速度跟顺序执行相差不大,但在IO密集型的应用中,多线程比顺序执行会限制提升性能;多进程由于摆脱了GIL的限制,充分利用了CPU的资源,并行度最高,也获得更多的性能提升。
$ python pooling_with_concurrent_futures.py
item 1 result 10000000
item 2 result 20000000
item 3 result 30000000
item 4 result 40000000
item 5 result 50000000
item 6 result 60000000
item 7 result 70000000
item 8 result 80000000
item 9 result 90000000
item 10 result 100000000
Sequential execution in 7.084029 seconds
item 1 result 10000000
item 2 result 20000000
item 4 result 40000000
item 5 result 50000000
item 3 result 30000000
item 7 result 70000000
item 6 result 60000000
item 10 result 100000000
item 8 result 80000000
item 9 result 90000000
Thread pool execution in 6.836254 seconds
item 4 result 40000000
item 1 result 10000000
item 3 result 30000000
item 5 result 50000000
item 2 result 20000000
item 6 result 60000000
item 7 result 70000000
item 9 result 90000000
item 8 result 80000000
item 10 result 100000000
Process pool execution in 0.0213239999999999 seconds
结束语
在几乎所有需要处理大量客户端同时请求的服务器应用程序中,都会使用池技术。不过,许多应用程序要求任务应该立即执行,或者在需要对线程有更多的控制权的情况下,池并不是最佳选择。