我有以下函数,伪代码:
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
}
}
然而,这很快就陷入了僵局,因为,当顶级递归级别等待所有线程完成时,内部级别也会等待线程变为可用...
如何在没有死锁的情况下有效地并行化我的程序?
答案 0 :(得分:5)
将ThreadPoolExecutor用于具有依赖关系的任务时,您的问题是一个常见的设计问题。
我看到两个选项:
1)确保以自下而上的顺序提交任务,以便您永远不会有依赖于尚未启动的任务的正在运行的任务。
2)使用“直接切换”策略(参见ThreadPoolExecutor文档):
ThreadPoolExecutor executor = new ThreadPoolExecutor(poolSize, poolSize, 0, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
executor.setRejectedExecutionHandler(new CallerRunsPolicy());
这个想法是使用同步队列,以便任务永远不会在真正的队列中等待。拒绝处理程序负责处理没有可用线程的任务。使用此特定处理程序,提交程序线程将运行被拒绝的任务。
此执行程序配置可确保永远不会拒绝任务,并且您永远不会因任务间依赖性而导致死锁。
答案 1 :(得分:0)
你应该分两个阶段分开你的方法:
为此,您可以使用[Futures][1]
将结果设为异步。表示calc的所有结果都是Future [Result]类型。
立即返回Future将释放当前线程并为其他线程的处理留出空间。通过收集结果(新结果(结果)),您应该等待所有结果准备就绪(ScatterGather-Pattern,您可以使用信号量等待所有结果)。集合本身将走在一棵树上,检查(或等待结果到达)将在一个线程中发生。
总体而言,您构建了一个Futures树,用于收集结果并仅执行线程池中的“昂贵”操作。