生成无限并行流

时间:2017-08-23 06:09:36

标签: java java-8 rx-java java-stream

问题

嗨,我有一个函数,我将返回无限的并行流(是的,在这种情况下它更快)生成的结果。很明显(或不是)我用过

Stream<Something> stream = Stream.generate(this::myGenerator).parallel()

它有效但是......当我想限制结果时它没有(当流顺序时一切都很好)。我的意思是,当我制作像

这样的东西时,它会产生结果
stream.peek(System.out::println).limit(2).collect(Collectors.toList())

但即使peek输出产生的元素超过10个,collect仍然没有被确认(生成速度很慢,所以10个甚至可能需要一分钟)......这就是一个简单的例子。实际上,限制这些结果是一个 future ,因为主要的期望是只获得比最近的结果更好,直到用户将杀死进程(其他情况是首先返回我可以通过抛出异常,如果没有否则会帮助[findFirst没有,即使我在控制台上有更多元素而且在30秒内没有更多结果]。

所以,问题是......

如何复制?我的想法也是使用RxJava,还有另一个问题 - 如何使用该工具(或其他)获得类似的结果。

代码示例

public Stream<Solution> generateSolutions() {
     final Solution initialSolution = initialSolutionMaker.findSolution();
     return Stream.concat(
          Stream.of(initialSolution),
          Stream.generate(continuousSolutionMaker::findSolution)
    ).parallel();
}

new Solver(instance).generateSolutions()
    .map(Solution::getPurpose)
    .peek(System.out::println)
    .limit(5).collect(Collectors.toList());

findSolution的实施并不重要。 它有一些副作用,比如添加到解决方案repo(singleton,sych等......),但仅此而已。

2 个答案:

答案 0 :(得分:4)

already linked answer中所述,高效并行流的关键点是使用已具有内在大小的流源,而不是使用未大小的甚至无限的流并应用limit它。注入大小根本不适用于当前的实现,同时确保已知大小不会丢失更容易。即使无法保留确切的大小,例如在应用filter时,大小仍将作为估算大小。

所以而不是

Stream.generate(this::myGenerator).parallel()
      .peek(System.out::println)
      .limit(2)
      .collect(Collectors.toList())

只需使用

IntStream.range(0, /* limit */ 2).unordered().parallel()
         .mapToObj(unused -> this.myGenerator())
         .peek(System.out::println)
         .collect(Collectors.toList())

或者,更接近您的示例代码

public Stream<Solution> generateSolutions(int limit) {
    final Solution initialSolution = initialSolutionMaker.findSolution();
    return Stream.concat(
         Stream.of(initialSolution),
         IntStream.range(1, limit).unordered().parallel()
               .mapToObj(unused -> continuousSolutionMaker.findSolution())
   );
}

new Solver(instance).generateSolutions(5)
    .map(Solution::getPurpose)
    .peek(System.out::println)
    .collect(Collectors.toList());

答案 1 :(得分:3)

不幸的是,这是预期的行为。我记得我至少看过两个关于这个问题的主题,这里是one of them

我们的想法是Stream.generate创建unordered infinite streamlimit不会引入SIZED标记。因此,当您在该流上生成parallel执行时,各个任务必须同步其执行以查看它们是否已达到该限制;到同步发生时,可能已经处理了多个元素。例如:

 Stream.iterate(0, x -> x + 1)
            .peek(System.out::println)
            .parallel()
            .limit(2)
            .collect(Collectors.toList());

和此:

IntStream.of(1, 2, 3, 4)
            .peek(System.out::println)
            .parallel()
            .limit(2)
            .boxed()
            .collect(Collectors.toList());

将始终在ListCollectors.toList)中生成两个元素,而将始终输出两个元素(通过peek)。

另一方面,这个:

Stream<Integer> stream = Stream.generate(new Random()::nextInt).parallel();

List<Integer> list = stream
            .peek(x -> {
                System.out.println("Before " + x);
            })
            .map(x -> {
                System.out.println("Mapping x " + x);
                return x;
            })
            .peek(x -> {
                System.out.println("After " + x);
            })
            .limit(2)
            .collect(Collectors.toList());

将在List中生成两个元素,但它可以处理更多元素,稍后将被limit丢弃。这就是您在示例中实际看到的内容。

唯一明智的做法(据我所知)将是创建一个自定义Spliterator。我没有写过很多,但这是我的尝试:

 static class LimitingSpliterator<T> implements Spliterator<T> {

    private int limit;

    private final Supplier<T> generator;

    private LimitingSpliterator(Supplier<T> generator, int limit) {
        Preconditions.checkArgument(limit > 0);
        this.limit = limit;
        this.generator = Objects.requireNonNull(generator);
    }

    @Override
    public boolean tryAdvance(Consumer<? super T> consumer) {
        if (limit == 0) {
            return false;
        }
        T nextElement = generator.get();
        --limit;
        consumer.accept(nextElement);
        return true;
    }

    @Override
    public LimitingSpliterator<T> trySplit() {

        if (limit <= 1) {
            return null;
        }

        int half = limit >> 1;
        limit = limit - half;
        return new LimitingSpliterator<>(generator, half);
    }

    @Override
    public long estimateSize() {
        return limit >> 1;
    }

    @Override
    public int characteristics() {
        return SIZED;
    }
}

用法是:

 StreamSupport.stream(new LimitingSpliterator<>(new Random()::nextInt, 7), true)
            .peek(System.out::println)
            .collect(Collectors.toList());