指定核心数时foreach填充矩阵结果丢失的原因及解决方法
关于doParallel并行计算结果缺失的问题分析与解决
问题现象
未指定核心数时运行代码:
library(doParallel) n <- 100 n_rep <- 10 MM <- array(0, dim = c(n_rep,2)) set.seed(12345678) res <- foreach( sml = 1:n_rep, .combine = 'c' )%dopar% { X <- cbind(rnorm(n), rnorm(n)) MM[sml,] <- colMeans(X) res <- list(MM) return(res) } registerDoSEQ() res[n_rep]
得到完整矩阵结果:
[[1]] [,1] [,2] [1,] -0.07927541 0.141971071 [2,] 0.06443829 -0.053686746 [3,] 0.35348693 0.079771715 [4,] -0.15080328 0.039228709 [5,] -0.02202971 0.004544454 [6,] 0.21505184 0.102926773 [7,] 0.10736964 -0.103377088 [8,] 0.01214953 0.084428114 [9,] -0.10305094 0.055477455 [10,] -0.15185766 -0.003602193
指定4个核心运行代码:
library(doParallel) n <- 100 n_rep <- 10 num_core <- 4 cl <- makeCluster(num_core) registerDoParallel(cl) on.exit(stopCluster(cl)) MM <- array(0, dim = c(n_rep,2)) set.seed(12345678) res <- foreach( sml = 1:n_rep, .combine = 'c' )%dopar% { X <- cbind(rnorm(n), rnorm(n)) MM[sml,] <- colMeans(X) res <- list(MM) return(res) } registerDoSEQ() res[n_rep]
得到的矩阵出现部分结果缺失:
[[1]] [,1] [,2] [1,] 0.00000000 0.000000000 [2,] -0.04088736 0.054177509 [3,] 0.00000000 0.000000000 [4,] 0.00000000 0.000000000 [5,] 0.00000000 0.000000000 [6,] -0.14591851 0.029542600 [7,] 0.00000000 0.000000000 [8,] 0.06478407 -0.004580664 [9,] 0.00000000 0.000000000 [10,] -0.04621458 0.085551408
原因分析
- 并行进程的内存隔离:用
makeCluster创建集群时,每个核心对应独立的工作进程,拥有专属内存空间。主进程定义的MM矩阵不会被工作进程共享,工作进程里修改的MM只是各自内存中的副本,既不影响主进程的MM,也无法被其他工作进程读取。 - 未指定核心数的本质:未显式创建集群时,doParallel默认使用单线程的
doSEQ模式(名义并行实际串行),所有循环在同一内存空间执行,修改的是同一个MM矩阵,因此能得到完整结果。
解决方法
放弃跨进程修改共享变量的思路,改为让每个工作进程返回当前迭代的计算结果,最后在主进程中合并结果:
修正后的代码
library(doParallel) n <- 100 n_rep <- 10 num_core <- 4 cl <- makeCluster(num_core) registerDoParallel(cl) on.exit(stopCluster(cl)) # 并行环境下需用安全的随机种子设置方式 set.seed(12345678, kind = "L'Ecuyer-CMRG") res <- foreach(sml = 1:n_rep, .combine = rbind) %dopar% { X <- cbind(rnorm(n), rnorm(n)) colMeans(X) # 返回当前迭代的列均值结果 } # 最终结果即为合并后的矩阵 MM <- res MM
关键修改点
- 移除共享变量跨进程修改:每个迭代仅返回当前计算的
colMeans(X),不再尝试修改主进程的矩阵。 - 调整
.combine参数:使用rbind将每个迭代返回的二维向量直接合并为最终矩阵,替代原有的c。 - 并行安全的随机种子:使用
kind = "L'Ecuyer-CMRG"设置种子,确保并行环境下随机数生成的一致性,避免结果混乱。
内容的提问来源于stack exchange,提问作者user0131
相关产品推荐
相关产品推荐

