java中的并发队列消耗谜

时间:2015-03-28 22:49:33

标签: java multithreading opencv java-8 java-stream

尝试使用Java OpenCV加速我的图像处理,我尝试使用并行流来使用OpenCV <Mat>的队列。如果我对算法进行计时并计算队列中剩余的内容,则在并行处理流时会得到不一致的结果,但顺序计算结果是正确的。由于我使用ConcurrentLinkedQueue(),我认为我对线程安全和异步性都很好,但显然不是。有谁知道如何规避这个问题?

备注:

  • 元素在消费期间仍然被放入队列
  • 我正在运行一个4真实(8个虚拟)核心处理器

顺序流的结果:

  

帧集合起始大小(=生产):1455

     

帧集合结束大小(=生产 - 消费):1360

     

算法运行后的结果列表大小(=消耗):100

     

算法:6956 ms

并行流的结果:

  

帧集合起始大小(=生产):1455

     

帧集合结束大小(=生产 - 消费):440

     

算法运行后的结果列表大小(=消耗):100

     

算法:9242 ms

我的代码:

public class OvusculeTestConcurrent {

    public final static ConcurrentLinkedQueue<Mat> frameCollection = new ConcurrentLinkedQueue<Mat>();

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
        final String path = "C:\\Users\\Raoul\\workspace\\aorta2\\ressource\\artery_src_for_dual.avi";
        long startAlgoTime = System.nanoTime();

        // Constitute a frame collection in async mode
        Capture cap = new Capture(path, frameCollection);
        new Thread(cap).start();
        Thread.sleep(3000); //leaves time to accumulate frames
        System.out.println("frame collection start size (=production): "+frameCollection.size());

        //Consumes the current queue in parallel/sequential
        List<ImagePlus> lm = Stream.generate(() -> {
                return frameCollection.poll();
            })
            .parallel() // comment to disable parallel computing
            .limit(100L)
            .map(img -> utils.PrepareImage(img,
                                    new Point(300, 250),
                                    new Point(450, 250),
                                    new Point(400, 400),
                                    0.25))
            .collect(Collectors.toList());

        //timing & printing the results
        long endAlgoTime = System.nanoTime();
        long algoDuration = (endAlgoTime - startAlgoTime)/1_000_000;  //divide by 1_000_000 to get milliseconds.
        System.out.println("frame collection end size (=production - consumption): "+frameCollection.size());
        System.out.println("resulting list size after algorithm run (=consumption): "+lm.size());
        System.out.println("algorithm: "+algoDuration+" ms");
        System.exit(0);
    }

}

2 个答案:

答案 0 :(得分:1)

有一些事情引起了我的注意。

首先,使用Stream.generate创建流是在正确的轨道上。最好只调用queue.stream(),它将返回仅由队列的当前内容组成的流。您说在处理过程中元素正在添加到队列中,因此不起作用。

一个问题是生成这样的流的代码(为清晰起见而编辑):

Stream.generate(() -> queue.poll())

问题在于poll方法,其定义如下:

  

检索并删除此队列的头部,如果此队列为空,则返回null。

当流并行运行时,流的线程可以比生成元素并将其插入队列更快地排空队列。如果发生这种情况,队列将在某一点清空,并且流将填充null返回的poll元素。

我不确定PrepareImage在传递null时会做什么,但它似乎将某些内容传递给输出,这就是为什么你总是在目标列表中获得100个元素。

替代方法是使用BlockingQueue实现并使用take方法,例如

Stream.generate(() -> queue.take())

这将避免将空值注入流中。我不确定你应该使用哪个BlockingQueue实现,但是我建议你调查一个有大小限制的实现。如果您的生产者超过您的消费者,则无限制队列可能会扩展以填充所有可用内存。

不幸的是,BlockingQueue.take()会抛出InterruptedException,因此您无法在简单的lambda中使用它。你必须弄清楚在中断时要做什么。也许返回一个虚拟元素或其他东西。

另一个问题是limit方法对下游传递的元素数量施加了限制,但在并行流中,多个线程可能会机会性地拉出超过该数量的来自流的元素进行处理。这些操作由limit操作缓冲,直到达到其限制,此时流处理终止。从流源中提取并在达到限制时缓冲的任何元素都被简单地丢弃。这可能就是为什么从队列中提取了超过1,000个元素,但结果列表中只有100个元素结束。

(但即使在连续的情况下数字也没有加起来。我认为同样的事情不会发生,当达到限制时缓冲的元素被丢弃。也许是因为附加元素是在加工过程中产生?)

如果您可以使用被丢弃的元素,那么由queue.take()提供的并行流可能会起作用;否则,需要采用不同的方法。

答案 1 :(得分:0)

我有类似的问题,无法在网络上找到解决方案。想出我自己的public class QueueDrainSpliterator<T> implements Spliterator<T> { private final BlockingQueue<T> elements; public QueueDrainSpliterator(BlockingQueue<T> elements) { this.elements = elements; } @Override public boolean tryAdvance(Consumer<? super T> action) { T el = elements.poll(); if (el != null) { action.accept(el); return true; } return false; } @Override public Spliterator<T> trySplit() { if (!elements.isEmpty()) { BlockingQueue<T> split = new LinkedBlockingQueue<T>(); elements.drainTo(split, (int) Math.ceil(elements.size() / 2d)); return new QueueDrainSpliterator<T>(split); } return null; } @Override public long estimateSize() { return elements.size(); } @Override public int characteristics() { return Spliterator.NONNULL | Spliterator.CONCURRENT; } } ,看起来像SpliteratorWithUnknownSize不支持并行性(没有研究过,只是看到所有处理都在同一个线程中完成)。这是:

StreamSupport.stream(new QueueDrainSpliterator<>(events), true)
                     .forEach(consumer);

用法示例:

elements.poll(long timeout, TimeUnit unit)

它将在队列中迭代资源。如果队列为空,它将完成。如果您需要等待生成元素,可以尝试使用animation