简体   繁体   English

递归并发

[英]Recursive concurrency

I have the following function, in pseudo-code: 我具有以下伪代码功能:

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
  }
}

I want to parallelize it, using a fixed number of threads. 我想使用固定数量的线程对其进行并行化。

My first attempt was: 我的第一次尝试是:

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
  }
}

However, this quickly got stuck in a deadlock, because, while the top recursion level waits for all threads to finish, the inner levels also wait for threads to become available... 但是,这很快陷入了僵局,因为,当顶级递归级别等待所有线程完成时,内部递归级别也等待线程变得可用...

How can I efficiently parallelize my program without deadlocks? 如何有效地并行化程序而不会出现死锁?

Your problem is a general design problem when using ThreadPoolExecutor for tasks with dependencies. 当您将ThreadPoolExecutor用于具有依赖项的任务时,您的问题是一个常规设计问题。

I see two options: 我看到两个选择:

1) Make sure to submit tasks in a bottom-up order, so that you never have a running task that depends on a task which didn't start yet. 1)确保以自下而上的顺序提交任务,这样您就永远不会有依赖于尚未开始的任务的正在运行的任务。

2) Use the "direct handoff" strategy (See ThreadPoolExecutor documentation): 2)使用“直接切换”策略(请参阅ThreadPoolExecutor文档):

ThreadPoolExecutor executor = new ThreadPoolExecutor(poolSize, poolSize, 0, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
executor.setRejectedExecutionHandler(new CallerRunsPolicy());

The idea is using a synchronous queue so that tasks never wait in a real queue. 这个想法是使用同步队列,以便任务永远不会在真实队列中等待。 The rejection handler takes care of tasks which don't have an available thread to run on. 拒绝处理程序负责没有可用线程运行的任务。 With this particular handler, the submitter thread runs the rejected tasks. 使用此特定处理程序,提交者线程将运行被拒绝的任务。

This executor configuration guarantees that tasks are never rejected, and that you never have deadlocks due to inter-task dependencies. 此执行程序配置可确保任务永不被拒绝,并且由于任务间的依赖性而不会出现死锁。

you should split your approach in two phases: 您应该将方法分为两个阶段:

  1. create all the tree down until data.isFinal() == true 创建所有树直到data.isFinal()== true
  2. recursively collect the results (only possible if the merging does not produce other operations/calls) 递归收集结果(仅在合并不产生其他操作/调用的情况下才可能)

To do that, you can use [Futures][1] to make the results async. 为此,您可以使用[Futures][1]使结果异步。 Means all results of calc will be of type Future[Result]. 表示calc的所有结果均为Future [Result]类型。

Immediately returning a Future will free the current thread and give space for the processing of others. 立即返回一个Future将释放当前线程并为其他线程的处理留出空间。 With the collection of the Results (new Result(results)) you should wait for all results to be ready (ScatterGather-Pattern, you can use a semaphore to wait for all results). 使用结果集(新的结果(结果)),您应该等待所有结果准备就绪(ScatterGather-Pattern,您可以使用信号量等待所有结果)。 The collection itself will be walking a tree and checking (or waiting for the results to arrive) will happen in a single thread. 集合本身将走一棵树,并在单个线程中进行检查(或等待结果到达)。

Overall you build a tree of Futures, that is used to collect the results and perform only the "expensive" operations in the threadpool. 总体而言,您会构建一棵期货树,该树用于收集结果并仅在线程池中执行“昂贵”的操作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM