在多线程编程中,除了直接基于Thread进行多线程编程外,还可以基于concurrent.futures模块中的线程池,更加便捷地实现多线程编程。
多进程编程场景中,也有类似的进程池的实现,可以帮助我们简化多进程的开发,更加专注于业务逻辑的实现。
本文就来介绍两种使用进程池进行多进程编程的方法。
本文的主要内容有:
1、进程池的优势及适用场景
2、基于multiprocessing模块的进程池使用
3、基于concurrent.futures模块的进程池使用
进程池的优势及适用场景需要说明的是,如同多线程场景中的线程池一样,进程池在多进程编程中也提供了一种有效的资源管理和任务调度方式。
相较于直接使用Process进程多进程的开发,进程池有许多优势:
1、透明的资源管理
进程池能够帮我们进行进程资源的有效管理和重用,避免了每一次任务都要创建新进程的开销,降低对执行性能的影响。
2、简化代码
基于进程池,我们可以通过更高层次的接口,比如map()、submit()等来进行任务的提交,代码变得更加简介。
3、自动化管理
进程池会自动处理进程的创建、销毁和错误管理,不需要手动进行进程资源的管理,可以更加专注与业务逻辑的实现。
需要特别说明的是,如果只是简单的多进程场景,比如一个进程负责用户交互的响应,另一个进程在后台执行耗时的任务,则可以直接使用Process而无需使用进程池。因为,这种场景中,使用进程池并不能提升执行的性能。
进程池的适用场景主要有:
1、CPU密集型的任务
进程池特别适合处理CPU密集型任务,比如数值计算、图像处理、数据分析等。由于每个进程都有自己的Python解释器实例,可以充分利用多核CPU的能力。
2、大量的短时间任务
当有大量的短时间任务需要处理时,使用进程池可以有效避免频繁创建和销毁进程的开销,显著提高性能。
3、需要限流的场景
当需要控制并发进程的数量时,进程池允许用户设定最大进程数,避免过多进程导致系统资源耗尽的问题。
4、异步任务处理
进程池的异步任务提交和结果收集机制,可以使得任务处理变得更加灵活,特别适合需要非阻塞执行的场景。
5、任务分配和结果收集
进程池能够方便地将任务分配给多个进程,并在任务完成后收集结果,适合于需要批量处理大量数据的场景。
如果在需求场景中,需要有更大的灵活性、自定义进程的行为或者根据实时情况动态进行进程的管理,还是需要直接使用Process来进行多进程编程的。
基于multiprocessing模块的进程池使用首先来看,基于multiprocessing.Pool模块来进行进程池的编程。该模块的基本使用步骤如下:
1、创建一个Pool对象,并指定进程池中的可用进程数量,如果不指定,则默认是CPU核心数(不严谨)。
需要注意的是,os.cpu_count()默认情况下是返回系统可用的CPU核心的数量。然而,如果系统支持超线程,那么返回的值会是超线程 x 核心数。比如:笔者的电脑中,8核CPU且每个核心支持2个线程,则将返回16。
2、使用map()、apply()、apply_async()等方法提交任务到进程池。
3、关闭进程池,等待所有任务执行完成。
接下来,通过代码来看Pool的使用:
执行结果:
在执行的过程中,会发现先输出0~3的三次方,然后大约1秒后,输出4~7的三次方,最后输出剩下的3行,确实是最大同时进行4个任务的执行。
所以,map()方式的执行是一种同步的任务提交,适合于批量任务的提交,但是,同时需要限流执行的场景。
此外,还可以使用map_async()实现异步任务的提交,通过回调函数来实现结果的收集。
还是直接看代码:
执行结果:
从执行结果可以看出:
1、map_async()提交任务是异步的方式,无阻塞的,调用之后,会立即返回,而不会像map()方法一样,等待所有任务执行完成,才返回。
2、所有任务都执行完成后,会将每个任务的结果收集到一个list中,作为参数,进行回调函数的调用,从而实现异步调用结果的收集。
除了map()、map_async()之外,还有apply()、apply_async()、imap()、imap_unordered(),感兴趣的可以自行查阅相关文档,篇幅所限,这里就不一一展开了。
基于concurrent.futures模块的进程池使用除了multiprocessing.Pool之外,其实,我们有另一种更加便捷的进程池的使用方法,这就是concurrent.futures模块中的ProcessPoolExecutor。
使用这种方法的好处是,能够与多线程中的线程池ThreadPoolExecutor保持完全一致的编程接口。
最大的优势在于,如果有使用ThreadPoolExecutor编写的多线程的代码,仅仅将ThreadPoolExecutor修改为ProcessPoolExecutor即可完成从多线程改造为多进程。
比如,之前通过ThreadPoolExecutor进行斐波那契数列计算的代码如下:
这段代码就不进行解释了,遗忘的童鞋可以自行查看之前的线程池的文章。
我们只需要将Thread变更为Pool,既可以改成多进程,代码如下:
执行结果:
最后,简单比较一下这两种进程池的使用:
1、multiprocessing.Pool直接提供了进程池的管理接口,使用起来会更加灵活,但是需要手动管理进程的关闭和等待。
2、ProcessPoolExecutor则提供了更加简洁的API,是一种更高层次的抽象。而且,使用Future对象可以更好地管理任务的状态和结果,处理结果也更为方便。
需要说明的是,ProcessPoolExecutor模块实际上是建立在multiprocessing模块之上的,因此,可以将之视为对multiprocessing的一个更高层次的抽象。
总结本文首先介绍了使用进程池的优势,以及其适用的业务场景。然后,分别介绍了基于multiprocessing.Pool和concurrent.futures.ProcessPoolExecutor的进程池的创建和使用。最后,简单比较了两种实现方式。
以上就是本文的全部内容,感谢您的拨冗阅读,希望对您有所帮助。