doParallel "foreach" 不一致地从父环境继承对象:"Error in { : task 1 failed - " 找不到函数...”

doParallel "foreach" inconsistently inherits objects from parent environment: "Error in { : task 1 failed - "could not find function..."

我无法弄清楚 foreach 的问题。以下代码在我试过的两台 Windows 计算机上失败,但在三台 Linux 计算机上成功,所有 运行 相同版本的 R 和 doParallel:

library("doParallel")
registerDoParallel(cl=2,cores=2)

f <- function(){return(10)}
g <- function(){
    r = foreach(x = 1:4) %dopar% {
        return(x + f())
    }
    return(r)
}
g()

在这两台 Windows 计算机上,返回以下错误:

Error in { : task 1 failed - "could not find function "f""

然而,这在 Linux 计算机上工作得很好,并且在使用 %do% 而不是 %dopar% 时也工作得很好,并且在常规 for 循环中工作得很好。

变量也是如此,例如设置 i <- 10 并将 return(x + f()) 替换为 return(x + i)

对于遇到同样问题的其他人,两个解决方法是:

1) 使用 .export:

显式导入所需的函数和变量
r = foreach(x=1:4, .export="f") %dopar% 

2) 导入所有全局对象:

r = foreach(x=1:4, .export=ls(.GlobalEnv)) %dopar% 

这些变通办法的问题在于,对于一​​个正在积极开发的大型软件包来说,它们并不是最稳定的。在任何情况下,foreach 的行为都应该像 for.

知道是什么原因造成的吗?是否有解决办法?


使用该功能的电脑版本信息:

R version 3.2.2 (2015-08-14)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: CentOS release 6.5 (Final)

other attached packages:
[1] doParallel_1.0.10 iterators_1.0.8   foreach_1.4.3

无法使用该功能的电脑:

R version 3.2.2 (2015-08-14)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 7 x64 (build 7601) Service Pack 1

other attached packages:
[1] doParallel_1.0.10 iterators_1.0.8   foreach_1.4.3  

@Tensibai 说的对。当尝试在 Windows 上使用 doParallel 时,您必须 "export" 您想要使用的不在当前范围内的函数。根据我的经验,我完成这项工作的方式是使用以下(已编辑)示例。

format_number <- function(data) {
  # do stuff that requires stringr
}

format_date_time <- function(data) {
  # do stuff that requires stringr
}

add_direction_data <- function(data) {
  # do stuff that requires dplyr
}

parse_data <- function(data) {
  voice_start <- # vector of values
  voice_end <- # vector of values
  target_phone_numbers <- # vector of values
  parse_voice_block <- function(block_start, block_end, number) {
    # do stuff
  }

  number_of_cores <- parallel::detectCores() - 1
  clusters <- parallel::makeCluster(number_of_cores)
  doParallel::registerDoParallel(clusters)
  data_list <- foreach(i = 1:length(voice_start), .combine=list,
                       .multicombine=TRUE, 
                       .export = c("format_number", "format_date_time", "add_direction_data"), 
                       .packages = c("dplyr", "stringr")) %dopar% 
                       parse_voice_block(voice_start[i], voice_end[i], target_phone_numbers[i])
  doParallel::stopCluster(clusters)
  output <- plyr::rbind.fill(data_list)
}

由于前三个函数不包含在我当前的环境中,doParallel 会在启动 R 的新实例时忽略它们,但它会知道在哪里可以找到 parse_voice_block,因为它是当前范围内。此外,您需要指定 R 的每个新实例中应加载哪些包。正如 Tensibai 所说,这是因为您不是 运行 分叉进程,而是启动多个 R 实例和 运行 同时发出命令。

很遗憾,当您注册 doParallel 时使用:

registerDoParallel(2)

然后 doParallel 在 Linux 和 Mac OS X 上使用 mclapply,但是 clusterApplyLB 在 Windows。这通常会导致代码在 Linux 上工作但在 Windows 上失败,因为由于 fork 而使用 mclapply 时,工作人员是主服务器的克隆。出于这个原因,我通常使用以下方法测试我的代码:

cl <- makePSOCKcluster(2)
registerDoParallel(cl)

确保我正在加载所有必要的包并导出所有必要的函数和变量,然后切换回 registerDoParallel(2) 以在支持它的平台上获得 mclapply 的好处。

请注意,当 doParallel 使用 mclapply 时,.packages.export 选项将被忽略,但我建议始终使用它们以实现可移植性。


foreach 的 auto-export 特性在函数内部使用时效果不佳,因为 foreach 对于 auto-export 的内容相当保守。 auto-export 在当前环境中定义的变量和函数似乎很安全,但由于 R 的范围规则的复杂性,在这之外对我来说似乎有风险。

我倾向于同意你的评论,即你的两个 work-arounds 对于一个积极开发的包来说不是很稳定,但是如果 fg 是在包 foo,那么你应该使用 foreach .package 选项将包 foo 加载到 workers:

g <- function(){
    r = foreach(x = 1:4, .packages='foo') %dopar% {
        return(x + f())
    }
    return(r)
}

那么 f 将在 g 的范围内,即使它既没有被 foreach 隐式或显式导出。但是,这确实需要 ffoo 的导出函数(而不是内部函数),因为 worker 执行的代码没有在 foo 中定义,所以它只能访问导出的函数。 (很抱歉以两种不同的方式使用术语 "export",但这很难避免。)

我总是很想听听像您这样的评论,因为我一直在想是否应该调整 auto-export 规则。在这种情况下,我认为如果 foreach 循环由包中定义的函数执行,集群工作人员应该 auto-load 该包而不需要 .packages 选项。我会尝试研究一下,并可能将其添加到 doParalleldoSNOW.

的下一个版本中