[英]Recursive concurrency
我具有以下伪代码功能:
Result calc(Data data) {
if (data.isFinal()) {
return new Result(data); // This is the actual lengthy calculation
} else {
List<Result> results = new ArrayList<Result>();
for (int i=0; i<data.numOfSubTasks(); ++i) {
results.add(calc(data.subTask(i));
}
return new Result(results); // merge all results in to a single result
}
}
我想使用固定数量的线程对其进行并行化。
我的第一次尝试是:
ExecutorService executorService = Executors.newFixedThreadPool(numOfThreads);
Result calc(Data data) {
if (data.isFinal()) {
return new Result(data); // This is the actual lengthy calculation
} else {
List<Result> results = new ArrayList<Result>();
List<Callable<Void>> callables = new ArrayList<Callable<Void>>();
for (int i=0; i<data.numOfSubTasks(); ++i) {
callables.add(new Callable<Void>() {
public Void call() {
results.add(calc(data.subTask(i));
}
});
}
executorService.invokeAll(callables); // wait for all sub-tasks to complete
return new Result(results); // merge all results in to a single result
}
}
但是,这很快陷入了僵局,因为,当顶级递归级别等待所有线程完成时,内部递归级别也等待线程变得可用...
如何有效地并行化程序而不会出现死锁?
当您将ThreadPoolExecutor用于具有依赖项的任务时,您的问题是一个常规设计问题。
我看到两个选择:
1)确保以自下而上的顺序提交任务,这样您就永远不会有依赖于尚未开始的任务的正在运行的任务。
2)使用“直接切换”策略(请参阅ThreadPoolExecutor文档):
ThreadPoolExecutor executor = new ThreadPoolExecutor(poolSize, poolSize, 0, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
executor.setRejectedExecutionHandler(new CallerRunsPolicy());
这个想法是使用同步队列,以便任务永远不会在真实队列中等待。 拒绝处理程序负责没有可用线程运行的任务。 使用此特定处理程序,提交者线程将运行被拒绝的任务。
此执行程序配置可确保任务永不被拒绝,并且由于任务间的依赖性而不会出现死锁。
您应该将方法分为两个阶段:
为此,您可以使用[Futures][1]
使结果异步。 表示calc的所有结果均为Future [Result]类型。
立即返回一个Future将释放当前线程并为其他线程的处理留出空间。 使用结果集(新的结果(结果)),您应该等待所有结果准备就绪(ScatterGather-Pattern,您可以使用信号量等待所有结果)。 集合本身将走一棵树,并在单个线程中进行检查(或等待结果到达)。
总体而言,您会构建一棵期货树,该树用于收集结果并仅在线程池中执行“昂贵”的操作。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.