2009-06-28 5 views
1

모든 병렬 계산이 완료되었지만 '기본'스레드가 다른 작업과 함께 계속되는 경우가 종종 멈추는 동시 재귀 적 디렉터리 통과 및 파일 처리 프로그램을 만들었습니다.동시 디렉토리 탐색 알고리즘 정지 문제

코드는 기본적으로 포크 - 조인 스타일 동시 집계이고 병렬 집계가 완료된 후에는 결과를 스윙 창에 표시해야합니다. 집계의 문제점은 트리를 생성하고 계층 구조에서 리프 노드의 통계를 집계해야한다는 것입니다.

동시성 실수가 있었지만 찾을 수 없다고 확신합니다. 나는 포스트의 마지막 부분에 코드의 관련 부분을 포함시켰다. (간략하게하기 위해 코드 주석을 제거했다. 150 줄을 유감스럽게 생각한다. 필요하다면, 그것을 외부 위치로 옮길 수있다.)

컨텍스트 : Java 6u13, Windows XP SP3, 코어 2 듀오 CPU.

내 질문은 :

는 이것이 임의 정지의 원인이 될 수 있을까?

아마도 기존 라이브러리의 형태로 동시 디렉토리 트래버 설을 수행하는 더 좋은 방법이 있습니까?

더그 레아 (또는 Java 7) 통합/디렉토리 탐색을위한 더 나은 프레임 워크에서 포크 - 조인 프레임 워크가 될 것인가, 그렇다면 어떻게 내 구현을 다시 생각한다 - 개념적 수준에서?

감사합니다.

그리고 코드 발췌 :

private static JavaFileEvaluator[] processFiles(File[] files) 
throws InterruptedException { 
    CountUpDown count = new CountUpDown(); 
    ThreadPoolExecutor ex = (ThreadPoolExecutor)Executors 
    .newFixedThreadPool(Runtime.getRuntime().availableProcessors()); 

    JavaFileEvaluator[] jfes = new JavaFileEvaluator[files.length]; 
    for (int i = 0; i < jfes.length; i++) { 
     count.increment(); 
     jfes[i] = new JavaFileEvaluator(files[i], count, ex); 
     ex.execute(jfes[i]); 
    } 
    count.await(); 
    for (int i = 0; i < jfes.length; i++) { 
     count.increment(); 
     final JavaFileEvaluator jfe = jfes[i]; 
     ex.execute(new Runnable() { 
      public void run() { 
       jfe.aggregate(); 
      } 
     }); 

    } 
    // ------------------------------------- 
    // this await sometimes fails to wake up 
    count.await(); // <--------------------- 
    // ------------------------------------- 
    ex.shutdown(); 
    ex.awaitTermination(0, TimeUnit.MILLISECONDS); 
    return jfes; 
} 
public class JavaFileEvaluator implements Runnable { 
    private final File srcFile; 
    private final Counters counters = new Counters(); 
    private final CountUpDown count; 
    private final ExecutorService service; 
    private List<JavaFileEvaluator> children; 
    public JavaFileEvaluator(File srcFile, 
      CountUpDown count, ExecutorService service) { 
     this.srcFile = srcFile; 
     this.count = count; 
     this.service = service; 
    } 
    public void run() { 
     try { 
      if (srcFile.isFile()) { 
       JavaSourceFactory jsf = new JavaSourceFactory(); 
       JavaParser jp = new JavaParser(jsf); 
       try { 
        counters.add(Constants.FILE_SIZE, srcFile.length()); 
        countLines(); 
        jp.parse(srcFile); 
        Iterator<?> it = jsf.getJavaSources(); 
        while (it.hasNext()) { 
         JavaSource js = (JavaSource)it.next(); 
         js.toString(); 
         processSource(js); 
        } 
       // Some catch clauses here 
       } 
      } else 
      if (srcFile.isDirectory()) { 
       processDirectory(srcFile); 
      } 
     } finally { 
      count.decrement(); 
     } 
    } 
    public void processSource(JavaSource js) { 
     // process source, left out for brevity 
    } 
    public void processDirectory(File dir) { 
     File[] files = dir.listFiles(new FileFilter() { 
      @Override 
      public boolean accept(File pathname) { 
       return 
       (pathname.isDirectory() && !pathname.getName().startsWith("CVS") 
       && !pathname.getName().startsWith(".")) 
       || (pathname.isFile() && pathname.getName().endsWith(".java") 
       && pathname.canRead()); 
      } 
     }); 
     if (files != null) { 
      Arrays.sort(files, new Comparator<File>() { 
       @Override 
       public int compare(File o1, File o2) { 
        if (o1.isDirectory() && o2.isFile()) { 
         return -1; 
        } else 
        if (o1.isFile() && o2.isDirectory()) { 
         return 1; 
        } 
        return o1.getName().compareTo(o2.getName()); 
       } 
      }); 
      for (File f : files) { 
       if (f.isFile()) { 
        counters.add(Constants.FILE, 1); 
       } else { 
        counters.add(Constants.DIR, 1); 
       } 
       JavaFileEvaluator ev = new JavaFileEvaluator(f, count, service); 
       if (children == null) { 
        children = new ArrayList<JavaFileEvaluator>(); 
       } 
       children.add(ev); 
       count.increment(); 
       service.execute(ev); 
      } 
     } 
    } 
    public Counters getCounters() { 
     return counters; 
    } 
    public boolean hasChildren() { 
     return children != null && children.size() > 0; 
    } 
    public void aggregate() { 
     // recursively aggregate non-leaf nodes 
     if (!hasChildren()) { 
      count.decrement(); 
      return; 
     } 
     for (final JavaFileEvaluator e : children) { 
      count.increment(); 
      service.execute(new Runnable() { 
       @Override 
       public void run() { 
        e.aggregate(); 
       } 
      }); 
     } 
     count.decrement(); 
    } 
} 
public class CountUpDown { 
    private final Lock lock = new ReentrantLock(); 
    private final Condition cond = lock.newCondition(); 
    private final AtomicInteger count = new AtomicInteger(); 
    public void increment() { 
     count.incrementAndGet(); 
    } 
    public void decrement() { 
     int value = count.decrementAndGet(); 
     if (value == 0) { 
      lock.lock(); 
      try { 
       cond.signalAll(); 
      } finally { 
       lock.unlock(); 
      } 
     } else 
     if (value < 0) { 
      throw new IllegalStateException("Counter < 0 :" + value); 
     } 
    } 
    public void await() throws InterruptedException { 
     lock.lock(); 
     try { 
      if (count.get() > 0) { 
       cond.await(); 
      } 
     } finally { 
      lock.unlock(); 
     } 
    } 
} 

편집 추가 hasChildren() JavaSourceEvaluator의 방법.

답변

1

JavaFileEvaluator의 집계 메소드에서 count.decrement()는 finally 블록에서 호출되지 않습니다. 임의의 RuntimeExceptions가 집계 함수 (아마도 시체가 보이지 않는 hasChildren 메서드)에 던져지면 감소에 대한 호출은 결코 일어나지 않을 것이며 CountUpDown은 무기한 기다리게 될 것입니다. 이것은보고있는 무작위 정지의 원인 일 수 있습니다.

두 번째 질문의 경우 Java에서이 작업을 수행하는 라이브러리를 알 수는 없지만 실제로 보지 않았습니다. 응답이 없으므로 유감스럽게 생각합니다. 그러나이 작업은 내가 할 수있는 기회가 아닙니다. 전에 사용하십시오.

세 번째 질문이 나오면 다른 누군가가 제공 한 포크 - 조인 (fork-join) 프레임 워크를 사용하는지 또는 자신의 동시성 프레임 워크를 계속 제공하는지, 가장 큰 이득은 통과 작업을 수행하는 논리를 분리하는 것입니다 parrallelism 관리와 관련된 논리의 디렉토리. 제공 한 코드는 CountUpDown 클래스를 사용하여 모든 스레드가 완료되는 시점을 추적하고 디렉토리 통과를 다루는 메소드 전체에 뿌리가 내린 증가/감소 호출로 끝나며 이로 인해 악의적 인 버그를 추적합니다. java7 포크 - 조인 (fork-join) 프레임 워크로 이동하면 실제 순회 논리만을 처리하는 클래스를 작성하고 동시성 논리를 프레임 워크에 남겨두면 좋은 방법이 될 수 있습니다.다른 옵션은 여기에있는 내용을 계속 사용하면서 관리 논리와 작업 논리를 명확하게 구분하여 이러한 종류의 버그를 추적하고 수정하는 데 도움이됩니다.

+0

와우, 네 말이 맞아! 누락 된 hasChildren을 추가했습니다. 시체를 try-finally로 포장하여 잠시 동안 루프에서 코드를 실행합니다. 감사합니다! +1. 당신은 또한 두 번째와 세 번째 질문에 반영 할 수 있을까요? – akarnokd

+0

1 년 전에이 코드를 처음 시작했을 때 라이브러리를 찾지 못했습니다. 이런 종류의 병렬 트래버스는 fj 프레임 워크에 아웃소싱 된 것 같습니다. 고맙습니다. – akarnokd