Многопоточность и рекурсия вместе
У меня есть рекурсивный код, который сначала обрабатывает древовидную структуру. Код в основном выглядит так:
function(TreeNode curr)
{
if (curr.children != null && !curr.children.isEmpty())
{
for (TreeNode n : curr.children)
{
//do some stuff
function(n);
}
}
else
{
//do some other processing
}
}
Я хочу использовать потоки, чтобы сделать это быстрее. Большая часть времени тратится на перемещение, поэтому я не хочу просто создавать поток для обработки "другой обработки", потому что это не займет много времени. Я думаю, что хочу развить темы На "сделать некоторые вещи", но как это будет работать?
3 ответов
Это хороший случай для Fork / Join framework который должен быть включен в Java 7. В качестве отдельной библиотеки для использования с Java 6 можно скачать здесь.
что-то вроде этого:
public class TreeTask extends RecursiveAction {
private final TreeNode node;
private final int level;
public TreeTask(TreeNode node, int level) {
this.node = node;
this.level = leve;
}
public void compute() {
// It makes sense to switch to single-threaded execution after some threshold
if (level > THRESHOLD) function(node);
if (node.children != null && !node.children.isEmpty()) {
List<TreeTask> subtasks = new ArrayList<TreeTask>(node.children.size());
for (TreeNode n : node.children) {
// do some stuff
subtasks.add(new TreeTask(n, level + 1));
}
invokeAll(subtasks); // Invoke and wait for completion
} else {
//do some other processing
}
}
}
...
ForkJoinPool p = new ForkJoinPool(N_THREADS);
p.invoke(root, 0);
ключевым моментом Fork / join framework является кража работы-в ожидании завершения подзадач поток выполняет другие задачи. Это позволяет писать алгоритм простым способом, избегая при этом проблем с исчерпанием потока как наивный подходы с ExecutorService
бы.
на // do some stuff
блок кода, где вы работаете на отдельном узле, вместо этого вы можете отправить узел в какой-то ExecutorService
(в виде Runnable
которая будет работать на узле).
вы можете настроить ExecutorService
что вы используете для поддержки пула определенного количества потоков, что позволяет отделить логику" обработки " (наряду с логикой создания потоков, сколько создавать и т. д.) От вашей логики анализа дерева.
Это решение предполагает, что обработка происходит только в листовых узлах и что фактическая рекурсия дерева не занимает много времени.
Я бы попросил вызывающий поток выполнить рекурсию, а затем BlockingQueue
рабочих, которые обрабатывают листья через пул потоков. Я не справляюсь с InterruptedException
в нескольких местах здесь.
public void processTree(TreeNode top) {
final LinkedBlockingQueue<Runnable> queue =
new LinkedBlockingQueue<Runnable>(MAX_NUM_QUEUED);
// create a pool that starts at 1 threads and grows to MAX_NUM_THREADS
ExecutorService pool =
new ThreadPoolExecutor(1, MAX_NUM_THREADS, 0L, TimeUnit.MILLISECONDS, queue,
new RejectedExecutionHandler() {
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
queue.put(r); // block if we run out of space in the pool
}
});
walkTree(top, pool);
pool.shutdown();
// i think this will join with all of the threads
pool.awaitTermination(WAIT_TILL_CHILDREN_FINISH_MILLIS, TimeUnit.MILLISECONDS);
}
private void walkTree(final TreeNode curr, ExecutorService pool) {
if (curr.children == null || curr.children.isEmpty()) {
pool.submit(new Runnable() {
public void run() {
processLeaf(curr);
}
});
return;
}
for (TreeNode child : curr.children) {
walkTree(child, pool);
}
}
private void processLeaf(TreeNode leaf) {
// ...
}