【问题标题】:doParallel foreach scoping issue: Error in eval(expr, envir, enclos) : could not find functiondoParallel foreach 范围问题:eval 中的错误(expr、envir、enclos):找不到函数
【发布时间】:2013-11-01 08:43:12
【问题描述】:

我想在每个doParallel 线程中执行一个tclTaskSchedule 计时器(来自tcltk2 包)。但是,以下最小代码

library(doParallel)

n <- detectCores()
cl <- makeCluster(n, outfile="out.log")
registerDoParallel(cl)

testfn <- function() print(paste("hello from", i))
foreach(i=1:n, .packages = c("tcltk", "tcltk2"), .verbose = T) %dopar% {
    tclTaskSchedule(1000, testfn(), id = paste0("task", i), redo = 10)
}

stopCluster(cl)

导致错误(在控制台打印不是,而是在out.log

Error in eval(expr, envir, enclos) : could not find function "testfn"

但是,从 .verbose = T 参数中,我可以从控制台看到 testfn 正在导出:

automatically exporting the following variables from the local environment:
  testfn

确实,用.export = "testfn" 调用foreach 会导致同样的错误。

那么到底出了什么问题?


(我为什么要这样做?最后,我想在一定的时间间隔异步地poll多个数据源,并且每个数据源都有自己特定的轮询间隔)

【问题讨论】:

  • 我相信问题不在于dopar,而在于tclTaskSchedule,它对环境有一些内在的魔力。如果您只将print(testfn) 放入foreach 循环中,您将看到该函数是在worker 上定义的。然而,不知何故tclTaskSchedule 在错误的环境中寻找它。我尝试了一些东西,但找不到解决此问题的方法。 PS:如果你在没有并行化的情况下运行循环,你会发现你也有延迟评估的问题。

标签: r timer foreach parallel-processing polling


【解决方案1】:

我同意 Roland 的观点,即问题在于 tclTaskSchedule 评估其论点的方式。我的解决方案不是很漂亮,但我通过使用clusterExport 导出testfn 并在foreach 循环中将i 分配给worker 的全局环境来让它工作:

testfn <- function() print(paste("hello from", i))
clusterExport(cl, "testfn")

foreach(i=1:n, .packages = c("tcltk", "tcltk2"), .verbose = F) %dopar% {
    i <<- i
    tclTaskSchedule(1000, testfn(), id = paste0("task", i), redo = 10)
}

我可能还会将.noexport="testfn" 传递给foreach,但这并不是必需的。

【讨论】:

    【解决方案2】:

    即使没有tclTaskSchedule,您也会发现另一个问题: 并行 foreach 循环中的print 语句不会输出 到交互式会话:

    来自a blog post

    foreach 的一个问题是它会为循环的每次迭代创建新的 RScript 实例,这会阻止将状态消息记录到控制台输出。

    一种解决方法是创建一个输出日志文件:

    cat("", file="log.txt")
    testfn <- function() cat("hello from", i, "\n", file="log.txt", append=TRUE)
    foreach(i=1:n, .packages = c("tcltk", "tcltk2"), .verbose = T) %dopar% {
        tclTaskSchedule(1000, testfn(), id = paste0("task", i), redo = 10)
    }
    

    然后可以使用tail -f log.txtbash 终端进行监控。

    【讨论】:

    • 由于代码使用outfile="log.txt",打印语句的输出无需修改testfn就进入log.txt。它还捕获错误消息,这些错误消息是确定问题原因的关键。
    • 请注意,foreach 不会为循环的每次迭代创建一个新的 Rscript 实例:这通常会非常低效。使用 doParallel 和 doSNOW,您可以通过调用 makeCluster 创建 Rscript 实例,这些“工作人员”将重复执行来自可能许多 foreach 循环的任务。