问候溢出者,
- 数据结构是任意数量节点的无环树。
- 较浅的节点依赖于较深节点的结果。
- 最终结果可以简单地通过递归遍历树来计算。
- 如果我有无限线程,我会为每个节点分配一个线程甚至更多。
- 分配给较浅节点的线程将等待较深节点的线程完成。
- 但是,我只有有限的线程;有时更多,有时少于总节点。
关于如何遍历这些树并最终以有限的线程获得最终结果的任何想法?
问候
问候溢出者,
关于如何遍历这些树并最终以有限的线程获得最终结果的任何想法?
问候
您想查看有向无环图 (DAG) 的拓扑排序。
如果图中的节点表示“作业”,则 DAG 的拓扑排序为您提供了必须完成的事情的顺序。维基百科页面具有得出订单的算法。
获得订单后,您的工作线程将开始使用此订单中的“作业”(元素)。在开始作业之前,线程需要检查相关作业是否已完成,但它们应该已经完成,或者正在由另一个线程进行。
既然你有一个树结构,你可以稍微特殊一下:简单地把子节点放在第一位,然后是他们的父母,等等,依次完成树的每一级。
此外,在问题上抛出“无限”数量的线程会引起人们的注意……除非您的工作通常是 I/O 绑定的,否则(CPU 数量 + 一些常量)线程似乎是合适的。
类库提供了灵活的线程池实现以及一些有用的预定义配置。您可以通过调用 Executors 中的静态工厂方法之一来创建线程池。我认为对于您的情况,应使用以下方法:
newFixedThreadPool - 创建一个固定大小的线程池,在提交任务时创建线程,直到最大池大小,然后尝试保持池大小不变(如果线程因意外异常而死亡,则添加新线程)。
在创建线程池期间,您可以设置池大小,但您可以向执行程序添加任意数量的线程(例如,每个节点的线程)。不会执行的线程将被排队。
如果你有一批计算要提交给 Executor,并且你想在结果可用时检索它们,你可以使用完成服务。CompletionService 结合了 Executor 和 BlockingQueue 的功能。您可以将 Callable 任务提交给它以供执行,并使用类似于 take 和 poll 方法的队列来检索已完成的结果,并在它们可用时打包为 Futures。ExecutorCompletionService 实现 CompletionService,将计算委托给 Executor。
这是在 Concurrency 书中使用 Java 中的 CompletionService 的示例:
public class Renderer {
private final ExecutorService executor;
Renderer(ExecutorService executor) { this.executor = executor; }
void renderPage(CharSequence source) {
final List<ImageInfo> info = scanForImageInfo(source);
CompletionService<ImageData> completionService =
new ExecutorCompletionService<ImageData>(executor);
for (final ImageInfo imageInfo : info)
completionService.submit(new Callable<ImageData>() {
public ImageData call() {
return imageInfo.downloadImage();
}
});
renderText(source);
try {
for (int t = 0, n = info.size(); t < n; t++) {
Future<ImageData> f = completionService.take();
ImageData imageData = f.get();
renderImage(imageData);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
throw launderThrowable(e.getCause());
}
}
}
对于 CPU 密集型任务,最佳线程数可能是您拥有的内核数或逻辑线程数。这可能是您机器上的 4、6 或 8。创建与可用物理线程数匹配的线程池的一种简单方法是。
ExecutorService pool = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors());
如果您的线程数多于内核数,则您的线程将不得不进行上下文切换,这会增加开销并使它们变慢。
这看起来很适合为 Java 7 开发的 Fork/Join 框架;它也可用于 Java 6,网址为http://gee.cs.oswego.edu/dl/concurrency-interest/(在 jsr166y 下)。您可以使用RecursiveTask
该类来表示您的计算并为数据结构中的子节点派生额外的任务。在http://download.oracle.com/javase/tutorial/essential/concurrency/forkjoin.html有一个简短的教程。