Python并行计算系列(一)入门篇

Python是生物信息学应用中的常用编程语言,在2019年11月TIOBE 编程语言排行榜中排名第3,仅次于Java语言、C语言。

“天下武功,唯快不破”——日益增长的生物医学海量数据向生物信息学工作者提出了时间上的要求。

在之前推文《Numba向量运算的强大 》中,Saber从软件层面着眼,向我们展示了通过numba模块加速,使Python的数学计算时间下降4-5个数量级。

本文,Edward将从硬件层面着眼,和读者一起学习Python如何调用多CPU实现并行计算,从而缩短生物信息分析时间。

全文共 2756字 0图

预计阅读时间:15 分钟

面向人群:1-8岁生物信息学开发者

关键字:Python 并行计算

01

多进程效果

通过两个例子,我们初步体会多进程的效果。

首先导入模块并准备一个自定义函数fun。代码如下:

代码语言:javascript
复制
import timeimport multiprocessing # Step I : 导入模块def fun(i):    print("process %s is starting" %i)    time.sleep(10) #间隔10秒,用于观察效果    print("process %s is ending" %i)

01

单线程模式

代码语言:javascript
复制
if __name__ == '__main__' :    start = time.perf_counter() #计时开始    for i in range(3):        fun(i)    end = time.perf_counter() #计时结束    print ("RUNTIME : %s seconds"%(end-start))

运行结果

process 0 is starting

process 0 is ending

process 1 is starting

process 1 is ending

process 2 is starting

process 2 is ending

RUNTIME :30.0011027seconds

02

例:多进程模式(以3个进程为例)

代码语言:javascript
复制
if __name__ == '__main__' :    start = time.perf_counter() #计时开始    pool = multiprocessing.Pool(processes=3) # Step II : 进程池    for i in range(3):        pool.apply_async(fun, (i,), )  # Step III : 异步(并行)计算    pool.close() # Step IV : 准备结束    pool.join() # Step IV : 完全结束    end = time.perf_counter() #计时结束    print ("RUNTIME : %s seconds"%(end-start))

运行结果

process 0 is starting

process 1 is starting

process 2 is starting

process 0 is ending

process 1 is ending

process 2 is ending

RUNTIME :10.2563386seconds

★ 测试平台 ★

CPU

Intel i7-8700 3.2GHz 6核12线程

Memory

Samsung 16G*1 DDR4 2400MHz

OS

Windows 10 Home Basic

Edition

PyCharm

我们可以对比得到,例1和例2有两处明显不同:

一是process处理顺序:

例1是0开始→0结束→1开始→1结束→2开始→2结束。

例2是0开始→1开始→2开始→0结束→1结束→2结束。

二是运行时间:

例2(三线程)只有例1(单线程)的大约1/3。

02

多进程实现代码解读

接下来,我们通过解释例2代码了解多进程是如何实现的。

1

导入多进程模块

代码语言:javascript
复制
    import multiprocessing

multiprocess是python自带的多进程模块,它允许我们分配任务到不同CPU逻辑核心上,满足CPU密集型计算(科学计算)的需求。

Tips

注意区分多进程、多线程、多协程3个不同的概念。

2

构造进程池

代码语言:javascript
复制
   pool = multiprocessing.Pool(processes=3)

这条命令的作用是:

(1)利用multiprocessing模块中的Pool类构造了一个进程池变量pool,用于存放、管理进程。

Tips

一个完整的Pool类包括5个参数:

  1. classmultiprocessing.Pool([
  2. processes[,
  3. initializer[,
  4. initargs[,
  5. maxtasksperchild[,
  6. context
  7. ]]]]])

在本文例子中只用到processes一个参数,实际上这些参数不设置一般也无大碍。

(2)processes参数设置为3。表示进程池的最大并发进程数量为3,即:允许同时运行的最大子进程数量是3。

Tips

之所以叫子进程,是因为它们是由主进程创建的。

主进程就是我们的主函数所对应的进程。

一个完整多进程体系

= 1个主进程

+ n个子进程(n∈正整数)

+ m个守护进程(m∈自然数)。

如果子进程数量大于3,那么超出部分将会排队,直到之前的进程运行完才运行。

如果不设置processes参数,默认值为CPU总逻辑核心数。

Tips

CPU核心可以分为物理核心、逻辑核心两个概念。

我们将在之后的推文中形象地解释他们。

3

向进程池投放子进程

代码语言:javascript
复制
      pool.apply_async(fun, (i,), )

apply_async multiprocessing.Pool (在本文中是进程池pool)的类属性,用于并行计算。

代码语言:javascript
复制
apply_async(func[, args[, kwds[, callback[, error_callback] ] ] ])

其中:

func表示放入进程池的函数名,在本文例子中是自定义函数fun ;

args是传递给func的参数列表,在本文例子中只有1个参数i ;

kwds为传递给func的关键字参数列表,在本文例子中没有用到 ;

callback用于指定func函数完成后的回调函数,在本文例子中没有用到 ;

error_callback用于指定func函数出错后的回调函数,在本文例子中没有用到 ;

我们会在之后的推文中继续介绍使用 callback、error_callback实现高级方法。

4

结束多进程,继续执行主函数其他语句

代码语言:javascript
复制
    pool.close() # 关闭进程池,不在向进程里增加新的子进程    pool.join() # 等待所有进程运行完毕后退出。

到此,我们就学完了一个最简单的多进程实例。