Java 将ExecutorService与要执行的任务树一起使用

Java 将ExecutorService与要执行的任务树一起使用,java,parallel-processing,executorservice,Java,Parallel Processing,Executorservice,我们有点问题。:) 我们希望确保在任何时候只有N个线程在执行后台任务。为此,我们使用了一个固定的线程池执行器。它似乎工作得很好 然后我们发现了一个问题。假设您有一个类,它使用executor执行一些并行工作,然后在executor线程中调用另一个类,executor线程也执行一些并行工作,打算等待它。下面是发生的情况: 主线程调用第一级方法 该方法认为它可以并行化为16个任务并将其工作分解 向执行人提交了16项任务 主线程开始等待其任务完成 假设有四个线程可用,那么前四个任务都会被选中并运行。

我们有点问题。:)

我们希望确保在任何时候只有N个线程在执行后台任务。为此,我们使用了一个固定的线程池执行器。它似乎工作得很好

然后我们发现了一个问题。假设您有一个类,它使用executor执行一些并行工作,然后在executor线程中调用另一个类,executor线程也执行一些并行工作,打算等待它。下面是发生的情况:

  • 主线程调用第一级方法
  • 该方法认为它可以并行化为16个任务并将其工作分解
  • 向执行人提交了16项任务
  • 主线程开始等待其任务完成
  • 假设有四个线程可用,那么前四个任务都会被选中并运行。因此,队列中还有12个任务
  • 现在,其中一个任务调用另一个方法
  • 这种新方法认为它可以并行化为两个任务。假设这是并行合并排序的第一步
  • 2个任务提交给执行者
  • 此线程现在开始等待其任务完成
哦。所以在这一点上,所有四个线程现在都将等待任务完成,但它们协同阻止实际运行这些任务的执行器

这个问题的解决方案1如下:在向执行器提交一个新任务时,如果我们已经在运行所有线程,并且已经在其中一个执行器线程上运行,则内联运行该任务。这在10个月内效果良好,但现在我们遇到了一个问题。如果它提交的新任务仍然相对较大,那么您可能会遇到新任务阻止该方法将其他任务添加到队列中的情况,否则其他工作线程将能够拾取这些任务。因此,当线程以内联方式处理工作时,会出现大量延迟


有没有更好的解决方案来解决执行潜在的无界后台任务树的核心问题?据我所知,相当于executor服务的.NET具有某种内置能力,可以从队列中窃取,从而防止原始死锁问题的发生,就我所知,这是一个理想的解决方案。但是在Java领域呢?

Java 7有一个
ForkJoinPool
的概念,它允许一个任务通过提交给同一个执行者来“分叉”另一个任务。然后,它可以选择稍后尝试“帮助加入”该任务,如果该任务尚未运行,则尝试运行该任务

我相信,在Java6中,通过简单地将
执行器
未来任务
组合,也可以完成同样的事情。像这样:

public class Fib implements Callable<Integer> {
    int n;
    Executor exec;

    Fib(final int n, final Executor exec) {
        this.n = n;
        this.exec = exec;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Integer call() throws Exception {
        if (n == 0 || n == 1) {
            return n;
        }

        //Divide the problem
        final Fib n1 = new Fib(n - 1, exec);
        final Fib n2 = new Fib(n - 2, exec);

        //FutureTask only allows run to complete once
        final FutureTask<Integer> n2Task = new FutureTask<Integer>(n2);
        //Ask the Executor for help
        exec.execute(n2Task);

        //Do half the work ourselves
        final int partialResult = n1.call();

        //Do the other half of the work if the Executor hasn't
        n2Task.run();

        //Return the combined result
        return partialResult + n2Task.get();
    }

}        
public类Fib实现了可调用{
int n;
遗嘱执行人;
Fib(最终整数n,最终执行人执行){
这个,n=n;
this.exec=exec;
}
/**
*{@inheritardoc}
*/
@凌驾
公共整数调用()引发异常{
如果(n==0 | | n==1){
返回n;
}
//划分问题
最终Fib n1=新的Fib(n-1,执行);
最终Fib n2=新的Fib(n-2,执行);
//FutureTask只允许运行完成一次
最终未来任务n2Task=新未来任务(n2);
//向遗嘱执行人寻求帮助
exec.execute(n2Task);
//我们自己做一半的工作
final int partialResult=n1.call();
//如果执行人没有完成,则完成另一半的工作
n2Task.run();
//返回组合结果
返回partialResult+n2Task.get();
}
}        

问题似乎在于,任务还试图并行化,这使得难以避免资源约束。你为什么要这么做?为什么不总是内联运行子任务


如果您已经通过并行化充分利用了cpu,那么您就不会通过将工作再次划分为较小的任务来购买太多的总体工作

您可以使用回调,而不是让线程等待任务完成。您的任务本身将需要回调,因为它们会提交更多任务

例如:

在主线程中,提交根任务并注册一些要执行的回调

由于所有子任务在其子任务报告完成之前都不会报告完成,因此在完成所有任务之前不应调用根回调


我是动态编写的,还没有测试或编译它,所以可能会有一些错误。

因为如果不在某个级别进行拆分,OP将只有一个大任务,只能由一个CPU来解决。分割问题是可以的,但是OP试图通过限制线程数量来减少上下文切换,现在的问题是大多数线程只是什么都不做。当然,当然。我理解这一点,但他没有给出任务持续时间的多少上下文,也没有给出相对于上下文切换的开销,您从该级别的并行化中可以获得多少好处。我希望OP能提供更多的背景。Tim的答案是其中的一半——有时你在顶层进行划分,然后其中一个顶层任务仍然比其他任务大(即,正是这种任务导致了我上面提到的解决方案出现问题)。另一个答案是,有时你称之为“底层”这是“最高级别”。你们解决问题了吗?有没有什么答案是你想要的?这似乎是一条路要走。我已经等不及Java7了。我的游泳池太完美了!
public class ParallelTask implements Runnable, Callback {
  private final Callback mCB;
  private final int mNumChildTasks;
  private int mTimesCalledBack = 0;
  private final Object mLock = new Object();
  private boolean mCompleted = false;
  public ParallelTask(Callback cb) {
    mCB = cb;
    mNumChildTasks = N; // the number of direct child tasks you know this task will spawn
    // only going down 1 generation
    // of course you could figure this number out in the run method (will need to be volatile if so)
   // just as long as it is set before submitting any child tasks for execution
  }

  @Override
  public void run() {
    // do your stuff
    // and submit your child tasks, but don't wait on them to complete
    synchronized(mLock) {
      mCompleted = true;
      if (mNumChildTasks == mTimesCalledBack) {
        mCB.taskCompleted();
      }
    }
  }

  // Callback interface
  // taskCompleted is being called from the threads that this task's children are running in
  @Override
  public void taskCompleted() {
    synchronized(mLock) {
      mTimesCalledBack++;
      // only call our parent back if our direct children have all called us back
      // and our own task is done
      if (mCompleted && mTimesCalledBack == mNumChildTasks) {
        mCB.taskCompleted();
      }
    }
  }
}