多进程与rpy2(包含ape)

2 投票
2 回答
564 浏览
提问于 2025-04-18 08:31

今天我遇到了一个问题,搞不明白为什么会这样。我有几个函数是串联在一起的,它们执行一些耗时的操作,作为一个更大流程的一部分。我把这些函数简化成了一个测试例子。问题是,当我直接调用一个函数时,我得到了预期的输出(比如,5棵不同的树)。但是,当我在一个多进程池中用apply_async(或者apply,没区别)调用同一个函数时,我得到了5棵树,但它们都是一样的。

我在一个IPython笔记本中记录了这个问题,可以在这里查看:http://nbviewer.ipython.org/gist/cfriedline/0e275d528ff1a8d674c6

在第91个单元中,我创建了5棵树(每棵树有10个小枝),并返回了两个列表。第一个列表包含了没有使用多进程的树,第二个列表是通过apply_async得到的。

在第92个单元中,你可以看到没有使用多进程创建树的结果,而在第93个单元中则是使用多进程的结果。

我期望在这两个测试中总共能得到10棵不同的树,但实际上所有的多进程树都是一样的。这让我很困惑。

相关的版本信息:

  • Linux 2.6.18-238.12.1.el5 x86_64 GNU/Linux
  • Python 2.7.6 :: Anaconda 1.9.2 (64位)
  • IPython 2.0.0
  • Rpy2 2.3.9

谢谢!

克里斯

2 个回答

1

我对这些库不是特别熟悉,不过在Linux系统上,multiprocessing这个模块(如果我没记错的话)是用os.fork来创建新进程的。这就意味着你用的随机数模块的状态也会被复制,这样每个进程生成的随机数序列就会是一样的,导致你调用的_get_random_string函数生成的字符串并不随机。

如果我说得没错,而且你创建的进程池数量少于你想要的树的数量,那么你会发现你得到的树会分成N组,每组都是一模一样的树(N就是你创建的进程池的数量)。

我觉得最理想的解决办法是在每个进程里重新设置随机数生成器的种子。这样它们不太可能在完全相同的时间运行,所以你应该能得到不同的结果。

2

我解决了这个问题,得到了@mgilson的指点。实际上,这是一个随机数的问题,只不过是在R语言中,而不是Python(唉)。在创建Pool的时候,R的状态会被复制,这也包括它的随机种子。要解决这个问题,只需要用一点rpy2,像下面这样调用R的set.seed函数(还加了一些特定于进程的内容以确保万无一失):

def create_tree(num_tips, type):
    """
    creates the taxa tree in R
    @param num_tips: number of taxa to create
    @param type: type for naming (e.g., 'taxa')
    @return: a dendropy Tree
    @rtype: dendropy.Tree
    """
    r = rpy2.robjects.r
    set_seed = r('set.seed')
    set_seed(int((time.time()+os.getpid()*1000)))
    rpy2.robjects.globalenv['numtips'] = num_tips
    rpy2.robjects.globalenv['treetype'] = type
    name = _get_random_string(20)
    if type == "T":
        r("%s = rtree(numtips, rooted=T, tip.label=paste(treetype, seq(1:(numtips)), sep=''))" % name)
    else:
        r("%s = rtree(numtips, rooted=F, tip.label=paste(treetype, seq(1:(numtips)), sep=''))" % name)
    tree = r[name]
    return ape_to_dendropy(tree)

撰写回答