MPI使用Python中的动态生成深度优先搜索

时间:2016-05-22 12:48:52

标签: multithreading python-2.7 mpi

好的,所以我想在树状结构中进行多线程深度优先搜索。我正在使用群集中多台计算机的线程(本例中为localhost四核和覆盆子pi 2)。主线程应该启动进程并在树中的第一个分割中,对于它分割成的每个节点,它应该生成一个新线程。然后,这些线程应该能够将他们的发现报告给主人。

我正在尝试动态地执行此操作,而不是为mpiexec提供多个线程,因为我不知道树预先看起来像什么(例如,可能有2或9个拆分)。

我从我正在为这个问题工作的项目中做了一个样本,我的工作如下。它从一串数字中取一位数,每个数字产生一个线程并将数字发送到该线程。

对于主人:

#!/usr/bin/python
from mpi4py import MPI
import datetime, sys, numpy, time

################ Set up MPI variables ################

comm = MPI.COMM_WORLD
rank = comm.Get_rank()
size = comm.Get_size()
name = MPI.Get_processor_name()
status = MPI.Status()

################ Master code ################

script = 'cpi.py'
for d in '34':
   try:
       print 'Trying to spawn child process...'
       icomm = MPI.COMM_SELF.Spawn(sys.executable, args=[script], maxprocs=1, root=0)
       spawnrank = icomm.Get_rank()
       icomm.send(d, dest=spawnrank, tag=11)
       print 'Spawned rank %d.' % spawnrank    
   except: ValueError('Spawn failed to start.')

solved = False
while solved == False:
    #while not comm.Iprobe(source=MPI.ANY_SOURCE, tag=MPI.ANY_TAG):
    #    print 'spawns doing some work...'
    #    time.sleep(1)
solved = comm.recv(source=MPI.ANY_SOURCE, tag=22)
print 'received solution: %d' % solved

它正确地产生了工人,他们收到了数字,但没有把它发回给主人。工人的代码如下:

#!/usr/bin/python
from mpi4py import MPI
import datetime, sys, numpy

################ Set up MPI variables ################

icomm = MPI.Comm.Get_parent()
comm = MPI.COMM_WORLD
irank = comm.Get_rank()
rank = comm.Get_rank()

running = True
while running:
    data = None
    data = icomm.recv(source=0, tag=11)
    if data:
        print 'Trying to send %s from worker rank %d to %d' % (data, rank, irank)
        icomm.send(data, dest=0, tag=22)
        break
print 'Worker on rank %d done.' % rank
icomm.Disconnect()

它永远不会到达主代码的最后一行。我还在主代码中添加(注释掉)探测器,以检查带有标记22的消息是否在某处悬挂,排除了recv函数中的错误,但探测器从未找到该消息。所以我认为它永远不会发送。

我认为通过打印两个进程的排名他们都使用等级0 这是有道理的,因为它们是在同一台计算机上生成的。但是当我添加一个hostfile和rankfile,试图强制它为奴隶使用另一台计算机时,它给了我以下错误:

[hch-K55A:06917] *** Process received signal ***
[hch-K55A:06917] Signal: Segmentation fault (11)
[hch-K55A:06917] Signal code: Address not mapped (1)
[hch-K55A:06917] Failing at address: 0x3c
[hch-K55A:06917] [ 0] /lib/x86_64-linux-gnu/libpthread.so.0(+0x10340) [0x7f2c0d864340]
[hch-K55A:06917] [ 1] /usr/lib/openmpi/lib/openmpi/mca_rmaps_rank_file.so(orte_rmaps_rank_file_lex+0x4a0) [0x7f2c0abdcb70]
[hch-K55A:06917] [ 2] /usr/lib/openmpi/lib/openmpi/mca_rmaps_rank_file.so(+0x23ac) [0x7f2c0abda3ac]
[hch-K55A:06917] [ 3] /usr/lib/libopen-rte.so.4(orte_rmaps_base_map_job+0x2e) [0x7f2c0dacd05e]
[hch-K55A:06917] [ 4] /usr/lib/libopen-rte.so.4(orte_plm_base_setup_job+0x5a) [0x7f2c0dac580a]
[hch-K55A:06917] [ 5] /usr/lib/openmpi/lib/openmpi/mca_plm_rsh.so(orte_plm_rsh_launch+0x338) [0x7f2c0b80a8c8]
[hch-K55A:06917] [ 6] /usr/lib/libopen-rte.so.4(+0x51ff4) [0x7f2c0dac3ff4]
[hch-K55A:06917] [ 7] /usr/lib/libopen-rte.so.4(opal_event_base_loop+0x31e) [0x7f2c0dae9cfe]
[hch-K55A:06917] [ 8] mpiexec() [0x4047d3]
[hch-K55A:06917] [ 9] mpiexec() [0x40347d]
[hch-K55A:06917] [10] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf5) [0x7f2c0d4b0ec5]
[hch-K55A:06917] [11] mpiexec() [0x403399]
[hch-K55A:06917] *** End of error message ***
Segmentation fault (core dumped)

使用的命令:mpiexec -np 1 --hostfile hostfile --rankfile rankfile python spawntest.py

HOSTFILE: 本地主机 localhost slots = 1 max-slots = 4 pi2 @ raspi2 slots = 4

Rankfile: rank 0 = localhost slot = 1 等级1 = pi2 @ raspi2 slot = 1-4

所以我的问题如下;如何在主计算机以外的计算机上生成这些线程,同时能够来回发送数据?

1 个答案:

答案 0 :(得分:3)

你的主人的代码非常错误,我觉得你缺乏对那里发生的事情的概念性理解。

MPI_COMM_SPAWN(或其mpi4py对等comm.Spawn())产生的作业中的MPI流程不会成为父MPI_COMM_WORLD的一部分。产生的进程形成一个完全独立的世界通信器,并通过 intercommunicator 与父作业相互链接,这正是spawn返回的内容。在您的情况下,icomm = MPI.COMM_SELF.Spawn(...)是主进程中的intercommunicator句柄。子作业中的进程使用MPI_COMM_GET_PARENT(mpi4py中的MPI.Comm.Get_parent())获取intercommunicator句柄。由于您正在产生单进程作业:

MPI.COMM_SELF.Spawn(sys.executable, args=[script], maxprocs=1, root=0)
                                                   ^^^^^^^^^^

在新成立的童工世界沟通者中只有一个流程,因此MPI.COMM_WORLD.Get_rank()在每个工人中都返回零。

这部分主人的代码是错误的,但由于交流员的实际工作方式,它仍然有效:

spawnrank = icomm.Get_rank() # <--- not what you expect
icomm.send(d, dest=spawnrank, tag=11)

Intercommunicators链接两组不同的进程。其中一个称为本地组,另一个称为远程组。在对讲机上使用MPI_COMM_RANKcomm.Get_rank())时,您可以在本地组中获取呼叫流程的等级。发送或接收时,指定的等级与远程组相关。在您的情况下,产生一个新工作人员会产生以下互通:

    mastet's MPI_COMM_SELF           child's MPI_COMM_WORLD
              |                                |
+=============|================================|=============+
|  +----------V----------+       +-------------V----------+  |
|  | group of the master |       | group of the child job |  |
|  |        [ 0 ]        |       |          [ 0 ]         |  |
|  +---------------------+       +------------------------+  |
|                    intercommunicator                       |
+============================================================+

(上面的传播者显示每个群体来自哪里;传播者本身不是互动者的一部分)

哪个组是本地组,哪个组是远程组取决于调用进程所属的组。主进程的本地组是子作业中的等级的远程组,反之亦然。这里重要的是,每个组的等级为0,因为组中至少有一个进程。您很幸运,主组中只有一个进程,因此icomm.Get_rank()返回0(并且它将始终返回零,因为主要的本地组来自MPI_COMM_SELF,它总是包含一个进程),它恰好(始终)是远程(子)组中的有效排名。正确的做法是将邮件发送到您知道在远程组中存在的固定排名,例如排名0

   icomm = MPI.COMM_SELF.Spawn(sys.executable, args=[script], maxprocs=1, root=0)
   icomm.send(d, dest=0, tag=11)

(此代码显式发送到远程组的排名0,而在此之前,值0只是一个幸运的巧合)

那就是说,发送部分 - 虽然不正确 - 仍然有效。接收部分没有,有几个原因。首先,您正在使用错误的通信器 - 从MPI_COMM_WORLD接收不起作用,因为子进程不是它的成员。事实上,MPI中的传播者是不可变的 - 如果不创建新的传播者,就无法添加或删除等级。您应该使用icomm从工作人员那里接收,就像您使用它发送给他们一样。现在,出现了第二个问题 - 主人中的icomm被每个新的Spawn覆盖,因此你实际上失去了与任何子工作进行通信的能力,但最后一个。你需要保留一个句柄列表并附加句柄。

接收部分有点复杂。没有MPI_ANY_COMM - 您无法接受涵盖所有子工作的接收操作,因为他们都住在他们各自的相互通信器中。您应该使用MPI_IPROBE在互通者列表上循环,或者(更好)从每个孩子开始非阻塞接收,然后使用MPI_WAIT_SOME(无论mpi4py等价物是什么)。

使用循环,主代码看起来应该是这样的(注意 - 未经测试的代码,我没有和/或使用mpi4py):

#!/usr/bin/python
from mpi4py import MPI
import datetime, sys, numpy, time

################ Set up MPI variables ################

comm = MPI.COMM_WORLD
rank = comm.Get_rank()
size = comm.Get_size()
name = MPI.Get_processor_name()
status = MPI.Status()

################ Master code ################

icomms = []
script = 'cpi.py'
for d in '34':
   try:
       print 'Trying to spawn child process...'
       icomm = MPI.COMM_SELF.Spawn(sys.executable, args=[script], maxprocs=1, root=0)
       icomm.send(d, dest=0, tag=11)
       icomms.append(icomm)
       print 'Spawned a child.'
   except: ValueError('Spawn failed to start.')

solved = False
while not solved and icomms:
    for icomm in icomms:
        if icomm.Iprobe(source=0, tag=MPI.ANY_TAG):
            print 'A child responded...'
            solved = icomm.recv(source=0, tag=MPI.ANY_TAG)
            icomm.Disconnect()
            icomms.remove(icomm)
            if solved: break
    if not solved:
        print 'spawns doing some work...'
        time.sleep(1)
# make sure all pending sends get matched
for icomm in icomms:
    icomm.recv(source=0, tag=MPI.ANY_TAG)
    icomm.Disconnect()
print 'received solution: %d' % solved

我希望你明白这一点。

添加:如果您从生成的作业中生成作业,则新子项无法轻松建立与顶级母版的连接。为此,您应该转向MPI-2客户端/服务器模型支持的一个不起眼的部分,让主服务器打开一个MPI_PORT_OPEN的端口,然后使用MPI_PUBLISH_NAME将其注册到MPI命名服务,并且最后使用MPI_COMM_ACCEPT接收来自任何其他MPI作业的连接。工作人员应使用MPI_LOOKUP_NAME获取对端口的引用,并使用MPI_COMM_CONNECT与主作业建立互通。我不知道mpi4py中是否存在这些函数的包装器,如果存在,那么这些函数是如何命名的。

相关问题