使用R的foreach() %dopar%生成的PDF损坏无法读取的原因及解决方法
问题根因
- 用
type='FORK'创建并行集群时,子进程会复制父进程的内存空间和文件描述符,但R的图形设备状态是进程私有的。子进程中调用dev.set()切换的是父进程PDF设备的副本,而非父进程中实际的设备实例。 - PDF文件需要在设备关闭时写入关键的交叉引用表和EOF标记来完成文件结构收尾,这些操作必须由创建该设备的进程(父进程)完成,但子进程对设备的写入没有同步更新父进程中设备的内部状态,导致父进程调用
dev.off()时无法生成完整的PDF收尾信息,最终文件损坏。
修复方案
由于并行进程无法安全共享父进程的图形设备,正确的做法是让每个并行任务独立完成PDF文件的打开、绘图、关闭流程。如果坚持要在外部管理文件,需要通过文件路径而非设备编号传递,让子进程自己打开对应文件:
修改后的代码
library(ggplot2) library(parallel) library(doParallel) library(foreach) # 修改makeplot函数,直接接收文件路径,自行管理设备生命周期 makeplot <- function(file_path) { pdf(file_path) plt <- ggplot(cars) + geom_point(aes(x=speed, y=dist)) for(ii in seq(10)) {print(plt)} dev.off() # 子进程内自行关闭,完成PDF收尾 } # 串行文件 fser <- c('test_serial_one.pdf', 'test_serial_two.pdf') # 并行文件 fpar <- c('test_parallel_one.pdf', 'test_parallel_two.pdf') # 串行处理 for(f in fser) {makeplot(f)} # 并行处理 cl <- makeCluster(2, type='FORK') registerDoParallel(cl) foreach(f=fpar) %dopar% {makeplot(f)} stopCluster(cl) # 记得关闭集群
关于“外部打开关闭PDF”的补充说明
如果必须在父进程打开所有PDF文件,需要通过文件句柄而非R设备编号传递,让子进程直接写入文件,但这种方式需要手动处理PDF的底层结构,复杂度极高,远不如让子进程独立管理设备可靠,因此不推荐。
内容的提问来源于stack exchange,提问作者stachyra
相关产品推荐
相关产品推荐

