1、基本概念

多进程库提供了Pool类来实现简单的多进程任务。Pool类有以下方法:

- apply():直到得到结果之前一直阻塞。

- apply_async():这是apply()方法的一个变体,返回的是一个result对象。这是一个异步的操作,在所有的子类执行之前不会锁住主进程。

- map():这是内置的map函数的并行版本,在得到结果之前一直阻塞,此方法将可迭代的数据的每一个元素作为进程池的一个任务来执行。

- map_async():这是map的一个变体,返回一个result对象。如果指定了回调函数,回调函数应该是callable的,并且只接受一个参数。当result准备好时,会自动调用回调函数,除非调用失败。回调函数应该立即完成,否则,持有result的进程将被阻塞。

2、测试用例

创建四个进程池,然后使用map方法进行一个简单的计算。

import multiprocessing

def function_square(data):
result = data * data
return result if __name__ == "__main__":
inputs = list(range(100))
pool = multiprocessing.Pool(processes=4)
pool_outputs = pool.map(function_square, inputs)
pool.close()
pool.join()
print("pool: ", pool_outputs)

pool.map方法将一些独立的任务提交给进程池。pool.map和内置map的执行结果相同,但pool.map是通过多个并行进程计算的。

3、mpi4py模块

Python提供了很多MPI模块写并行程序。其中mpi4py在MPI-1/2顶层构建,提供了面向对象的接口,紧跟C++绑定的MPI-2。MPI是C语言用户可以无需学习新的接口就可以使用这个库。

此模块包含的主要的应用:

- 点对点通讯

- 集体通讯

- 拓扑

4、安装mpi4py

安装mpich:https://www.microsoft.com/en-us/download/confirmation.aspx?id=56727

下载并安装msmpisetup.exe

安装完成后安装目录如下:

将bin目录添加到系统环境中:

用cmd输入并显示如下即为安装成功

安装mpi4py

pip install mpi4py

MPI测试用例

from mpi4py import MPI

def mpi_test(rank):
print("I am rank %s" %rank) if __name__ == "__main__": comm = MPI.COMM_WORLD
rank = comm.Get_rank()
mpi_test(rank)
print("Hello world from process", rank)

使用mpi运行文件

在MPI中,并行程序中不同进程用一个非负整数来区别,如果我们有P个进程,那么rank会从0到P-1分配。

MPI拿到rank的函数如下:rank = comm.Get_rank()

这个函数返回调用它的进程的rank,comm叫做交流者,用于区别不同的进程集合:comm = MPI.COMM_WORLD

5、MPI点对点通讯

MPI提供的最实用的一个特性是点对点通讯。两个不同的进程之间可以通过点对点通讯交换数据:一个进程是接收者,一个进程是发送者。

Python的mpi4py通过下面两个函数提供了点对点通讯功能:

- Comm.Send(data, process_destination):通过它在交流组中的排名来区分发送给不同进程的数据。

- Comm.Recv(process_source):接收来自源进程的数据,也是通过在交流组中的排名来分分的。

Comm变量表示交流着,定义了可以互相通讯的进程组:

comm  = MKPI.COMM_WORLD

交换信息测试用例:

from mpi4py import MPI

comm = MPI.COMM_WORLD
rank = comm.rank
print("My rank is :",rank) if rank == 0:
data = 10000000
destination_process = 4
comm.send(data, dest=destination_process)
print("sending data %s to process %d" %(data, destination_process)) if rank == 1:
destination_process = 8
data = "hello,I am rank 1" comm.send(data, dest=destination_process)
print("sending data %s to process %d" %(data, destination_process)) if rank == 4:
data = comm.recv(source=0)
print("data received is = %s" %data) if rank == 8:
data1 = comm.recv(source=1)
print("data received is = %s" %data1)

运行结果:

通过mpiexec -n 9运行9个互相通讯的进程,使用rank的值来区分每个进程。

整个过程分为两部分,发送者发送数据,接收者接收数据,二者必须都指定发送方/接收方,source=为指定发送者。如果有发送的数据没有被接收,程序会阻塞。

comm.send()和comm.recv()函数都是阻塞的函数,他们会一直阻塞调用者,直到数据使用完成,同时在MPI中,有两种方式发送和接收数据:

- buffer模式

- 同步模式

在buffer模式中,只要需要发送的数据被拷贝到buffer中,执行权就会交回到主程序,此时数据并非已经发送/接收完成。在同步模式中,只有函数真正的结束发送/接收任务之后才会返回。

6、避免死锁

mpi4py没有提供特定的功能来解决这种情况,但是提供了一些程序员必须遵守的规则来避免死锁的问题。

出现死锁的情况:

from mpi4py import MPI

comm = MPI.COMM_WORLD
rank = comm.rank
print("my rank is :",rank) if rank == 1:
data_send = "a"
destination_process = 5
source_process = 5
data_received = comm.recv(source=source_process)
comm.send(data_send, dest=destination_process) print("sending data %s to process %d" %(data_send, destination_process))
print("data received is = %s" %data_received) if rank == 5:
data_send = "b"
destination_process = 1
source_process = 1
data_received = comm.recv(source=source_process)
comm.send(data_send, dest=destination_process)
print("sending data %s to process %d" % (data_send, destination_process))
print("data received is = %s" % data_received)

运行结果:

进程1和进程5产生阻塞,程序阻塞。

此时两个进程都在等待对方,发生阻塞,因为recv和send都是阻塞的,两个函数都先使用的recv,所以调用者都在等待他们完成。所以讲上述代码改为如下即可解决阻塞:

from mpi4py import MPI

comm = MPI.COMM_WORLD
rank = comm.rank
print("my rank is :",rank) if rank == 1:
data_send = "a"
destination_process = 5
source_process = 5
comm.send(data_send, dest=destination_process)
data_received = comm.recv(source=source_process) print("sending data %s to process %d" %(data_send, destination_process))
print("data received is = %s" %data_received) if rank == 5:
data_send = "b"
destination_process = 1
source_process = 1
data_received = comm.recv(source=source_process)
comm.send(data_send, dest=destination_process)
print("sending data %s to process %d" % (data_send, destination_process))
print("data received is = %s" % data_received)

将其中一个函数的recv和send顺序调换。

运行结果:

也可通过Sendrecv函数解决,代码如下:

from mpi4py import MPI

comm = MPI.COMM_WORLD
rank = comm.rank
print("my rank is :",rank) if rank == 1:
data_send = "a"
destination_process = 5
source_process = 5
# comm.send(data_send, dest=destination_process)
# data_received = comm.recv(source=source_process)
data_received = comm.sendrecv(data_send, dest=destination_process, source=source_process) print("sending data %s to process %d" %(data_send, destination_process))
print("data received is = %s" %data_received) if rank == 5:
data_send = "b"
destination_process = 1
source_process = 1
# data_received = comm.recv(source=source_process)
# comm.send(data_send, dest=destination_process)
data_received = comm.sendrecv(data_send, dest=destination_process, source=source_process)
print("sending data %s to process %d" % (data_send, destination_process))
print("data received is = %s" % data_received)

运行结果:

7、集体通讯:Broadcast

在并行代码的开发中,会经常需要在多个进程间共享某个变量运行时的值,或操作多个进程提供的变量。MPI库提供了在多个进程之间交换信息的方法,将所有进程变成通讯者的这种方法叫做集体交流。因此,一个集体交流通常是2个以上的进程,也可以称为广播——一个进程将消息发送给其他进程。mpi4py模块通过以下方式提供广播的功能:

buf = comm.bcast(data_to_share, rank_of_root_process)

这个函数将root消息中包含的信息发送给属于comm通讯组其他的进程,每个进程必须通过相同的root和comm来调用它。

测试代码:

from mpi4py import MPI

comm = MPI.COMM_WORLD
rank = comm.Get_rank() if rank == 0:
variable_to_share = 100
else:
variable_to_share = None variable_to_share = comm.bcast(variable_to_share, root=0)
print("process = %d variable shared = %d" %(rank, variable_to_share))

运行结果:

rank等于0的root进程初始化了一个变量,variable_to_share,值为100,然后声明了一个广播variable_to_share = comm.bcast(variable_to_share, root=0)

这个变量将通过通讯组发送给其他进程。

集体通讯允许组中的多个进程同时进行数据交流。在mpi4py模块中,只提供了阻塞版本的集体通讯(阻塞调用者,直到缓存中的数据全部安全发送。)

广泛应用的集体通讯应该是:

- 组中的进程提供通讯的屏障

- 通讯方式包括:

- 将一个进程的数据广播到组中其他进程中

- 从其他进程收集数据发给一个进程

- 从一个进程散播数据到其他进程中

- 减少操作

8、集体通讯:Scatter

scatter函数和广播很像,但是不同的是comm.bcast将相同的数据发送给所有在监听的进程,comm.scatter可以将数据放在数据中,发送给不同的进程。

comm.scatter函数接收一个array,根据进程的rank将其中的元素发给不同的进程,第一个元素发送给进程0,第二个元素发给进程1,以此类推。

测试用例:

from mpi4py import MPI

comm = MPI.COMM_WORLD
rank = comm.Get_rank() # array_to_share = ["a","b","c","d","e","f","g","h","i","j"]
if rank == 0:
array_to_share = [0,1,2,3,4,5,6,7,8,9]
else:
array_to_share = None recvbuf = comm.scatter(array_to_share, root=0)
print("Process = %d recvbuf = %s" %(rank, recvbuf))

执行结果:

注意:列表中的元素个数,需要个进程保持一致。否则会出现如下错误。

9、集体通讯:gather

gather函数基本上是反向的scatter,即收集所有进程发送到root进程数据。方法如下:

recvbuf = comm.gather(sendbuf, rank_of_root_process)

sendbuf是要发送的数据,rank_of_root_process代表要接收数据的进程。

测试用例:

from mpi4py import MPI

comm = MPI.COMM_WORLD
size = comm.Get_size()
# print(size)
rank = comm.Get_rank()
data = "process %s" %rank
# print("start %s"%data)
data = comm.gather(data, root=0)
# print(data)
if rank == 0:
print("rank = %s receiving data to other process" %rank)
for i in range(1, size):
#data[i] = (i+1) ** 2
value = data[i]
print("process %s receiving %s from process %s" %(rank, value, i))
# print(data)

执行结果:

10、使用Alltoall通讯

Alltoall集体通讯结合了scatter和gather的功能。在mpi4py中,有以下三类的Alltoall集体通讯。

- comm.Alltoall(sendbuf, recvbuf);

- comm.Alltoallv(sendbuf, recvbuf);

- comm.Alltoallw(sendbuf, recvbuf);

Alltoall测试用例:

from mpi4py import MPI
import numpy comm = MPI.COMM_WORLD
size = comm.Get_size()
rank = comm.Get_rank() a_size = 1 # print("numpy arange: %s" %numpy.arange(size, dtype=int))
senddata = (rank+1)*numpy.arange(size, dtype=int)
recvdata = numpy.empty(size * a_size, dtype=int)
print("senddata is %s , recvdata is %s" %(senddata, recvdata))
# print("Recvdata is %s: , \n numpy.empty is %s" %(recvdata, numpy.empty(size * a_size, dtype=int))) comm.Alltoall(senddata, recvdata)
print("process %s sending %s, receiving %s" %(rank, senddata, recvdata))

运行结果:

comm.alltoall方法将task j的sendbuf的第j个对象拷贝到task i中,recvbuf的第j个对象,一一对应。发送过程如图:

可以将左右两个方格看做xy轴,结果一一对应,如左图的(0,0)对应的值为0,其对应的有图的值为右图的(0,0)也为0。左图的3,4对应的值为16,右图(4,3)也为16。

P0包含的数据[0 1 2 3 4],它将值0赋值给自己,1传给进程P1,2传给进程P2,3传给进程P3,以此类推。

相同的P1的数据为[0 2 4 6 8] , 它将0传给P0,2传给P1,4传给P2,以此类推。

All-to-all定制通讯也叫全部交换,这种操作经常用于各种并发算法中,比如快速傅里叶变换,矩阵变换,样本排序以及一些数据库的 Join 操作。

11、简化操作

同comm.gather一样,comm.reduce接收一个数组,每一个元素是一个进程的输入,然后返回一个数组,每一个元素是进程的输出,返回给root进程。输出的元素包含了简化的结果。

简化定义如下:comm.Reduce(sendbuf, recvbuf, rank_of_root_process, op = type_of_reduction_operation)

这里需要注意的是,参数op和comm.gather不同,它代表你想应用在数据上的操作,mpi4py模块代表定义了一系列的简化操作,包括:

- MPI.MAX:返回最大的元素

- MPI.MIN:返回最小的元素

- MPI.SUM:对所有的元素相加

- MPI.PROD:对所有元素相乘

- MPI.LAND:对所有元素进行逻辑操作

- MPI.MAXLOC:返回最大值,以及拥有它的进程

- MPI.MINLOC:返回最小值,以及拥有它的进程

测试用例:

import numpy as np
from mpi4py import MPI comm = MPI.COMM_WORLD
size = comm.size
rank = comm.rank
array_size = 3
recvdata = np.zeros(array_size, dtype=np.int)
senddata = (rank+1)*np.arange(size, dtype=np.int)
print("+++++++++++++%s+++++++++++++%s++++++++++++" %(recvdata, senddata))
print("Process %s sending %s" %(rank, senddata))
comm.Reduce(senddata, recvdata, root=0, op=MPI.SUM)
print("on task %s, after Reduce: data = %s" %(rank, recvdata))

执行结果:

MPI.SUM为求和操作,过程如下:

简化操作将每个task的第i个元素相加,然后放回到P0进程(root进程)的第i个元素中。

Python并行编程(十三):进程池和mpi4py模块的更多相关文章

  1. python并发编程之进程池,线程池,协程

    需要注意一下不能无限的开进程,不能无限的开线程最常用的就是开进程池,开线程池.其中回调函数非常重要回调函数其实可以作为一种编程思想,谁好了谁就去掉 只要你用并发,就会有锁的问题,但是你不能一直去自己加 ...

  2. python并发编程之进程池,线程池concurrent.futures

    进程池与线程池 在刚开始学多进程或多线程时,我们迫不及待地基于多进程或多线程实现并发的套接字通信,然而这种实现方式的致命缺陷是:服务的开启的进程数或线程数都会随着并发的客户端数目地增多而增多, 这会对 ...

  3. Python并发编程之进程池与线程池

    一.进程池与线程池 python标准模块concurrent.futures(并发未来) 1.concurrent.futures模块是用来创建并行的任务,提供了更高级别的接口,为了异步执行调用 2. ...

  4. python并发编程之进程池、线程池、协程

    需要注意一下不能无限的开进程,不能无限的开线程最常用的就是开进程池,开线程池.其中回调函数非常重要回调函数其实可以作为一种编程思想,谁好了谁就去掉 只要你用并发,就会有锁的问题,但是你不能一直去自己加 ...

  5. Python网络编程(进程池、进程间的通信)

    线程池的原理:        线程池是预先创建线程的一种技术.线程池在还没有任务到来之前,        创建一定数量的线程,放入空闲队列中.这些线程都是处于睡眠状态,        即均为启动,不消 ...

  6. 《转载》Python并发编程之线程池/进程池--concurrent.futures模块

    本文转载自Python并发编程之线程池/进程池--concurrent.futures模块 一.关于concurrent.futures模块 Python标准库为我们提供了threading和mult ...

  7. Python 多进程编程之 进程间的通信(在Pool中Queue)

    Python 多进程编程之 进程间的通信(在Pool中Queue) 1,在进程池中进程间的通信,原理与普通进程之间一样,只是引用的方法不同,python对进程池通信有专用的方法 在Manager()中 ...

  8. python自带的进程池及线程池

    进程池 """ python自带的进程池 """ from multiprocessing import Pool from time im ...

  9. Python 多进程编程之 进程间的通信(Queue)

    Python 多进程编程之 进程间的通信(Queue) 1,进程间通信Process有时是需要通信的,操作系统提供了很多机制来实现进程之间的通信,而Queue就是其中的一个方法----这是操作系统开辟 ...

随机推荐

  1. 【Java面试题】1 Java中使用switch-case的用法及注意事项超全总结

    今天在用到switch的时候,这种设计到最基本的内容,可能忘记它的一些基本语法,出现了一些错误,所以即兴从各种资料查询总结了下面的内容,希望可以帮助那些正在困扰switch错误和各种细节问题的朋友! ...

  2. datepicker防手动输入

    在<input>中加入readonly="readonly"

  3. Hyperic-Sigar简介

    Hyperic-Sigar是一个收集系统各项底层信息的工具集.他有如下特点:1. 收集信息全面收集CPU,MEM,NETWORK,PROCESS,IOSTAT等使用Sigar,你完全可以模仿出cpui ...

  4. JavaScript去空格之trim()

    <script> var str=" ab cd "; alert("["+str.trim()+"]"); </scri ...

  5. 第四章 Spring.Net 如何管理您的类___让对象了解自己的容器

    我们在开发中,经常需要让对象了解自己所在的容器的信息,例如,有时我们需要让对象知道,对象所在容器的引用是什么,或者是对象在容器中的名称是什么 .Spring.Net 中提供了两个接口,我们使用这两个接 ...

  6. 批量执行命令:fabric

    Fabric 可以通过 SSH 在多台客户端主机上批量执行任务,是基于 paramiko 封装开发的,paramiko 更底层一些,安装方法如下: [root@localhost ~]$ yum in ...

  7. 嵌入式Linux下Qt的中文显示

    一般情况下,嵌入式Qt界面需要中文显示,下面总结自己在项目中用到的可行的办法 1,下载一种中文简体字体,比如我用的是”方正准圆简体“,把字体文件放在ARM开发板系统的Qt字库中,即/usr/lib/f ...

  8. Android 使用CheckBox实现多选效果

    CheckBox:复选框1.有两种状态: 选中状态(true),未选中状态(false)2.属性: android:id="@+id/checkbox" android:layou ...

  9. nil、Nil、NULL与NSNull的区别及应用

      总结 nil:OC中的对象的空指针 Nil:OC中类的空指针 NULL:C类型的空指针 NSNull:数值类的空对象 详细解析应用如下: 1.nil 指向一个对象的指针为空 在objc.h中的定义 ...

  10. C/C++中的变量和静态变量

    static有两种用法:一是面向过程程序设计语言中的static,用于普通变量和函数,不涉及类:二是面向对象程序设计中的static,主要涉及static在类中的作用. 面向过程设计中的static ...