2011-03-10 3 views
3

우리는 약간의 문제가있었습니다. :)수행 할 작업 트리가있는 ExecutorService 사용

언제든지 N 개의 스레드 만 백그라운드 작업을 수행하도록하고 싶습니다. 이를 위해 고정 스레드 풀 실행 프로그램을 사용했습니다. 그것은 잘 작동하는 것 같았다.

그런 다음 문제가 발견되었습니다. 집행자를 사용하여 일부 병렬 작업을 수행하는 클래스가 있고 다른 병렬 처리를 수행하는 executor 스레드에서 다른 클래스를 호출한다고 가정하십시오. 여기에 일어나는 내용은 다음과 같습니다

  • 메인 스레드는 첫 번째 수준의 메소드를 호출합니다.
  • 이 방법은 16 개의 작업으로 병렬 처리하여 작업을 분할 할 수 있다고 생각합니다.
  • 16 개의 태스크가 집행자에게 제출됩니다.
  • 주 스레드가 완료 될 때까지 대기하기 시작합니다.
  • 사용할 수있는 스레드가 4 개인 경우 처음 네 개의 작업이 각각 선택되어 실행됩니다. 따라서 대기열에는 12 개의 작업이 남아 있습니다.
  • 이제 이러한 작업 중 하나는 다른 방법을 호출합니다.
  • 이 새로운 방법은 2 가지 작업으로 병렬 처리 할 수 ​​있다고 생각합니다. 병렬 병합 정렬의 첫 번째 단계라고 가정 해 보겠습니다.
  • 2 개의 태스크가 집행자에게 제출됩니다.
  • 이 스레드는 이제 해당 작업이 완료 될 때까지 대기하기 시작합니다.

어 - 오. 따라서이 시점에서 네 개의 모든 스레드는 작업이 완료 될 때까지 기다리고 있지만 실제로 작업을 실행하는 executor를 공동으로 차단합니다.

해결책 1은 executor에 새로운 태스크를 제출할 때 이미 모든 스레드를 실행 중이며 이미 executor 스레드 중 하나에서 실행 중이면 작업을 인라인으로 실행합니다. 이것은 10 개월 동안 정상적으로 작동했지만 지금은 문제가 발생했습니다. 제출하는 새 작업이 여전히 비교적 큰 경우 새 작업이 메서드가 다른 작업을 대기열에 추가하는 것을 차단하는 상황에 빠지게 될 수 있습니다. 다른 작업은 다른 작업자 스레드에 의해 선택 될 수 있습니다. 따라서 스레드가 작업을 인라인으로 처리하는 동안 엄청난 지연이 발생할 수 있습니다.

잠재적으로 제한되지 않는 백그라운드 작업 트리를 실행하는 핵심 문제에 대한 더 나은 해결책이 있습니까? 나는 Executor 서비스와 동등한 .NET이 원래의 교착 상태 문제가 발생하지 않도록 대기열에서 도용 할 수있는 내장형 기능을 가지고 있음을 이해합니다. 이상적인 해결책이라고 할 수 있습니다. 그러나 Java 토지의 경우는 어떨까요?

+0

그럼 당신들이 문제를 해결 했나요? 당신이 찾고 있던 답변이 있습니까? –

답변

3

Java 7은 ForkJoinPool이라는 개념을 사용하여 작업이 다른 작업을 동일한 Executor에 제출하여 "fork"할 수 있습니다. 그런 다음 실행되지 않은 경우 실행하려고 시도하여 나중에 해당 작업을 "조인 할"수있는 옵션을 제공합니다.

자바 12에서는 ExecutorFutureTask을 간단하게 결합하여 동일한 작업을 수행 할 수 있다고 생각합니다. 마찬가지로 :

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(); 
    } 

}   
+0

이것은 갈 길 인 것 같습니다. 그리고 Java 7을 기다릴 수 없습니다. ForkJoinPool은 * 완벽합니다 *! – Trejkaz

0

문제는 리소스 자체를 병렬 처리하여 리소스 제약을 피하기 어렵게 만드는 것 같습니다. 왜 이럴 필요가 있니? 항상 하위 작업을 인라인으로 실행하지 않는 이유는 무엇입니까?

이미 CPU를 병렬 처리로 완전히 활용하고 있다면 작업을 다시 작은 작업으로 나누어 전체 작업을 많이하지 않을 것입니다.

+0

일부 레벨에서 분리하지 않기 때문에 OP에는 단일 CPU로만 해결할 수있는 하나의 큰 작업이 있습니다. 문제를 분할하는 것은 괜찮지 만 OP는 스레드 수를 제한하여 컨텍스트 전환을 줄이기 위해 노력하고 있으며 이제는 대부분의 스레드가 단순히 아무 일도하지 않는 것이 문제입니다. –

+0

물론입니다. 나는 이해하지만 그는 작업 기간과 컨텍스트 스위칭의 오버 헤드에 비해 그 수준에서의 병렬화로부터 얻을 수있는 많은 이점에 대해 많은 내용을 제공하지 않았다. 나는 OP가 더 많은 맥락을 제공하기를 희망했다. – Jeremy

+0

팀의 대답은 절반입니다 - 때로는 최상위 수준에서 나누고 최상위 수준 작업 중 하나가 다른 것보다 큽니다 (즉 위에 언급 한 솔루션에서 문제를 일으키는 매우 종류의 작업). 다른 대답은 때로는 여기에서 "최하위 레벨"이라고 부르는 것이 "최상위 레벨"이라는 것입니다. – Trejkaz

1

스레드가 작업을 완료 할 때까지 기다리지 않고 콜백을 사용할 수 있습니다. 더 많은 작업을 제출하기 때문에 작업 자체가 콜백이어야합니다. 주 스레드에서

예컨대 :

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(); 
     } 
    } 
    } 
} 

당신은 당신의 루트 작업을 입력하고 실행해야 할 몇 가지 콜백을 등록합니다.

모든 하위 작업은 완료를보고 할 때까지 완료를보고하지 않으므로 모든 것이 완료 될 때까지 루트 콜백을 호출해서는 안됩니다.

비행 중에이 코드를 작성했지만 테스트 또는 컴파일하지 않았으므로 오류가있을 수 있습니다.

관련 문제