简体   繁体   English

使用 Callable 和 ExecutorCompletionService,future.cancel() 不起作用

[英]Using Callable and ExecutorCompletionService, future.cancel() does not work

I am using a bunch of callables to search a list in individual chunks, once one returns true, I want to cancel all the other running callables.我正在使用一堆可调用对象来搜索单个块中的列表,一旦返回 true,我想取消所有其他正在运行的可调用对象。 future.cancel is not cancelling them future.cancel 不会取消它们

My NumberFinder我的号码查找器

    public class NumberFinderImpl implements NumberFinder {
  // how many threads the exucutor will use to start the callables
  private final int NUMBER_THREADS = 20;
  // the amount of elements in the array we will search in each callable chunk
  private final int CHUNK_ARRAY_SIZE = 5;

  @Override
  public boolean contains(int valueToFind, List<CustomNumberEntity> arrayToSearch) {
    long startTime = System.nanoTime();
    ExecutorService WORKER_THREAD_POOL = Executors.newFixedThreadPool(NUMBER_THREADS);
    CompletionService<Boolean> completionService =
        new ExecutorCompletionService<>(WORKER_THREAD_POOL);
    int numberOfChunksNeeded = (int) Math.ceil(arrayToSearch.size() / CHUNK_ARRAY_SIZE);
    // get a callable for each chunk search
    List<Callable<Boolean>> callablesForEachChunkSearch =
        getCallablesForEachChunk(
            CHUNK_ARRAY_SIZE, numberOfChunksNeeded, valueToFind, arrayToSearch);
    // start the callables and collect the futures
    List<Future<Boolean>> futuresForCallables =
        callablesForEachChunkSearch
            .stream()
            .map(completionService::submit)
            .collect(Collectors.toList());
    for (int j = 0; j < futuresForCallables.size(); j++) {
      try {
        // take().get() is blocking
        // so if a callable is not done yet
        // it will wait until it is before proceeding
        Boolean chunkResult = completionService.take().get();
        if (chunkResult) {
          long endTime = System.nanoTime();
          long timeTaken = endTime - startTime;
          // TimeUnit
          long timeInSeconds = TimeUnit.SECONDS.convert(timeTaken, TimeUnit.NANOSECONDS);
          System.out.println("Search time in seconds" + timeInSeconds);
          for (Future<Boolean> future : futuresForCallables) {
            // cancel all the other running callables
            future.cancel(true);
          }
          return true;
        }
      } catch (InterruptedException e) {
        e.printStackTrace();
      } catch (ExecutionException e) {
        e.printStackTrace();
      }
    }
    for (Future<Boolean> future : futuresForCallables) {
      // cancel all the other running callables
      future.cancel(true);
    }
    long endTime = System.nanoTime();
    long timeTaken = endTime - startTime;
    // TimeUnit
    long timeInSeconds = TimeUnit.SECONDS.convert(timeTaken, TimeUnit.NANOSECONDS);
    System.out.println("Search time in seconds" + timeInSeconds);
    return false;
  }

  // get a list of callables that each search a certain chunk of the array
  private List<Callable<Boolean>> getCallablesForEachChunk(
      int chunkArraySize,
      int numberOfChunksNeeded,
      int valueToFind,
      List<CustomNumberEntity> arrayToSearch) {
    List<Callable<Boolean>> callableList = new ArrayList<>(numberOfChunksNeeded);
    for (int i = 0; i < numberOfChunksNeeded; i++) {
      int startPosForCallable = 0;
      if (i > 0) {
        startPosForCallable = i * chunkArraySize;
      }
      // dont let end pos go out of bounds
      // if the chunk extends past the size, just set endPos as the end of the array
      int endPosForCallable =
          Math.min(startPosForCallable + chunkArraySize - 1, arrayToSearch.size());
      Callable<Boolean> callableSearch =
          new NumberFinderCallable(
              arrayToSearch, valueToFind, startPosForCallable, endPosForCallable);
      callableList.add(callableSearch);
    }
    return callableList;
  }

My callable that does the searching我的可调用对象进行搜索

    public class NumberFinderCallable implements Callable<Boolean> {

  private List<CustomNumberEntity> arrayToSearch;
  private int startPos;
  private int endPos;
  private int valueToSearchFor;

  public NumberFinderCallable(
      List<CustomNumberEntity> arrayToSearch, int valueToSearchFor, int startPos, int endPos) {
    this.arrayToSearch = arrayToSearch;
    this.startPos = startPos;
    this.endPos = endPos;
    this.valueToSearchFor = valueToSearchFor;
  }

  @Override
  public Boolean call() {
    System.out.println(
        "Callable started, searching the chunk of array with start pos "
            + startPos
            + " and end pos "
            + endPos);
    for (int i = startPos; i <= endPos; i++) {
      System.out.println(
          "Callable is comparing a number in pos "
              + i
              + " in the chunk with star pos "
              + +startPos
              + " and end pos "
              + endPos);
      if (FastestComparator.compare(valueToSearchFor, arrayToSearch.get(i)) == 0) {
        System.out.println("element found in pos " + i + ". Returning true");
        return true;
      }
    }
    return false;
  }
}

I can see from the logs even after a true result is found and all the futures are cancelled that the threads are still going即使在找到真实结果并且所有期货都被取消后,我也可以从日志中看到线程仍在运行

Based on your previous question you have made it clear that you are trying to parallelize this contains method because of performance reasons and not for learning about the API.根据您之前的问题,您已经明确表示,由于性能原因,您正在尝试并行化此contains方法,而不是为了了解 API。 However, IMO you have made the mistake of assuming that this method actually needs to be optimized.但是,IMO 您错误地认为该方法实际上需要优化。

I have made an ad hoc test for a list with 100000000 (100 million) elements, and for the worst-case scenario i.e., trying to find an element that does not exist in the list.我对包含100000000 (1 亿)个元素的列表进行了临时测试,并针对最坏的情况,即尝试查找列表中不存在的元素。 For the sequential contain method对于顺序contain方法

list.contains(Integer.MAX_VALUE)

it took in average approximately:平均大约需要:

0.25 seconds

And the parallel version using streams:以及使用流的并行版本:

list.parallelStream().anyMatch(i -> i.equals(Integer.MAX_VALUE))

it took in average approximately:平均大约需要:

 0.19 seconds

A speedup of 1.32x in a 4 core machine.在 4 核机器中加速1.32x I highly doubt it that one will achieve a lot more than that.我非常怀疑一个人会取得比这更多的成就。 Not to mention the maintainability and readability of:更不用说以下的可维护性和可读性:

list.parallelStream().anyMatch(i -> i.equals(Integer.MAX_VALUE))

versus a potential parallel solution using explicitly executors and so on.与使用显式执行器等的潜在并行解决方案相比。

If the contain method is that important performance-wise, and one does not have duplicated elements in the list, one should consider using a HashSet for a constant complexity time contain method.如果contain方法在性能方面非常重要,并且列表中没有重复的元素,则应该考虑使用HashSet来获得恒定复杂度时间的contain方法。

Instead of ExecutorService而不是ExecutorService

ExecutorService WORKER_THREAD_POOL = Executors.newFixedThreadPool(NUMBER_THREADS);

Use ThreadPoolExecutor使用ThreadPoolExecutor

ThreadPoolExecutor WORKER_THREAD_POOL = (ThreadPoolExecutor) Executors.newFixedThreadPool(NUMBER_THREADS);

Then do a purge() to release threads of cancelled futures, eg然后执行purge()以释放已取消期货的线程,例如

future.cancel(true);
WORKER_THREAD_POOL.purge();

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

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