简体   繁体   English

灵活的CountDownLatch?

[英]Flexible CountDownLatch?

I have encountered a problem twice now whereby a producer thread produces N work items, submits them to an ExecutorService and then needs to wait until all N items have been processed. 我现在遇到了两次问题,即生产者线程生成N个工作项,将它们提交给ExecutorService ,然后需要等到所有N个项都被处理完毕。

Caveats 注意事项

  • N is not known in advance . N事先不知道 If it were I would simply create a CountDownLatch and then have producer thread await() until all work was complete. 如果是,我只需创建一个CountDownLatch ,然后让生产者线程await()直到所有工作完成。
  • Using a CompletionService is inappropriate because although my producer thread needs to block (ie by calling take() ) there's no way of signalling that all work is complete , to cause the producer thread to stop waiting. 使用CompletionService是不合适的,因为尽管我的生产者线程需要阻塞(即通过调用take() ),但没有办法表明所有工作都已完成 ,导致生产者线程停止等待。

My current favoured solution is to use an integer counter, and to increment this whenever an item of work is submitted and to decrement it when a work item is processed. 我目前最喜欢的解决方案是使用整数计数器,并在提交工作项时递增它,并在处理工作项时递减它。 Following the subsmission of all N tasks my producer thread will need to wait on a lock, checking whether counter == 0 whenever it is notified. 在所有N个任务的提交之后,我的生产者线程将需要等待一个锁,检查counter == 0是否通知。 The consumer thread(s) will need to notify the producer if it has decremented the counter and the new value is 0. 如果消费者线程已经递减计数器并且新值为0,则消费者线程将需要通知生产者。

Is there a better approach to this problem or is there a suitable construct in java.util.concurrent I should be using rather than "rolling my own"? 有没有更好的方法来解决这个问题,或者java.util.concurrent是否有合适的构造我应该使用而不是“滚动自己的”?

Thanks in advance. 提前致谢。

java.util.concurrent.Phaser looks like it would work well for you. java.util.concurrent.Phaser看起来适合你。 It is planned to be release in Java 7 but the most stable version can be found at jsr166 's interest group website. 计划在Java 7中发布,但最稳定的版本可以在jsr166的兴趣小组网站上找到。

The phaser is a glorified Cyclic Barrier. 移相器是一个美化的循环屏障。 You can register N number of parties and when youre ready await their advance at the specific phase. 您可以注册N个参与方,并在准备好等待特定阶段的预付款时。

A quick example on how it would work: 一个关于它如何工作的简单示例:

final Phaser phaser = new Phaser();

public Runnable getRunnable(){
    return new Runnable(){
        public void run(){
            ..do stuff...
            phaser.arriveAndDeregister();
        }
    };
}
public void doWork(){
    phaser.register();//register self
    for(int i=0 ; i < N; i++){
        phaser.register(); // register this task prior to execution 
        executor.submit( getRunnable());
    }
    phaser.arriveAndAwaitAdvance();
}

You could of course use a CountDownLatch protected by an AtomicReference so that your tasks get wrapped thus: 您当然可以使用受AtomicReference保护的CountDownLatch ,这样您的任务就会被包装:

public class MyTask extends Runnable {
    private final Runnable r;
    public MyTask(Runnable r, AtomicReference<CountDownLatch> l) { this.r = r; }

    public void run() {
        r.run();
        while (l.get() == null) Thread.sleep(1000L); //handle Interrupted
        l.get().countDown();
    }
}

Notice that the tasks run their work and then spin until the count-down is set (ie the total number of tasks is know). 请注意 ,任务运行他们的工作然后旋转,直到设置倒计时(即知道任务的总数)。 As soon as the count-down is set, they count it down and exit. 一旦设置倒计时,它们就会将其倒数并退出。 These get submitted as follows: 这些提交如下:

AtomicReference<CountDownLatch> l = new AtomicReference<CountDownLatch>();
executor.submit(new MyTask(r, l));

After the point of creation/submission of your work, when you know how many tasks you have created : 创建/提交作品之后, 当您知道创建了多少任务时

latch.set(new CountDownLatch(nTasks));
latch.get().await();

I've used an ExecutorCompletionService for something like this: 我已经使用了ExecutorCompletionService这样的东西:

ExecutorCompletionService executor = ...;
int count = 0;
while (...) {
    executor.submit(new Processor());
    count++;
}

//Now, pull the futures out of the queue:
for (int i = 0; i < count; i++) {
    executor.take().get();
}

This involves keeping a queue of tasks that have been submitted, so if your list is arbitrarily long, your method might be better. 这涉及保留已提交的任务队列,因此如果您的列表任意长,您的方法可能会更好。

But make sure to use an AtomicInteger for the coordination, so that you will be able to increment it in one thread, and decrement it in the worker threads. 但是请确保使用AtomicInteger进行协调,这样您就可以在一个线程中递增它,并在工作线程中递减它。

I assume your producer does not need to know when the queue is empty but needs to know when the last task has been completed. 我假设您的生产者不需要知道队列何时为空,但需要知道最后一个任务何时完成。

I would add a waitforWorkDone(producer) method to the consumer. 我会向waitforWorkDone(producer)添加waitforWorkDone(producer)方法。 The producer can add its N tasks and call the wait method. 生产者可以添加其N个任务并调用wait方法。 The wait method blocks the incoming thread iff the work queue is not empty and no tasks are executing at the moment. 如果工作队列不为空且当前没有任务正在执行,则wait方法会阻塞传入的线程。

The consumer threads notifyAll() on the waitfor lock iff its task has been finished, the queue is empty and no other task is being executed. 如果任务已完成,则队列为空且没有其他任务正在执行,消费者会在waitfor锁上对notifyAll()进行线程处理。

What you described is quite similar to using a standard Semaphore but used 'backwards'. 你所描述的与使用标准信号量非常相似,但使用了“向后”。

  • Your semaphore starts with 0 permits 您的信号量以0许可开始
  • Each work unit releases one permit when it completes 每个工作单元在完成时释放一个许可证
  • You block by waiting to acquire N permits 你等待获得N个许可证

Plus you have the flexibility to only acquire M < N permits which is useful if you want to check the intermediate state. 此外,您可以灵活地获得M <N许可证,如果您想要检查中间状态,这是非常有用的。 For example I'm testing an asynchronous bounded message queue so I expect the queue to be full for some M < N so I can acquire M and check that the queue is indeed full then acquire the remaining N - M permits after consuming messages from the queue. 例如,我正在测试异步有界消息队列,因此我希望队列在某些M <N时已满,因此我可以获取M并检查队列是否已满,然后在消耗来自消息的消息后获取剩余的N-M许可。队列。

Standalone Java 8+ method that does exactly this for a Stream using a single-pass Phaser . 独立的Java 8+方法,使用单程PhaserStream完成此操作。 Iterator / Iterable variants are exactly the same. Iterator / Iterable变体完全相同。

public static <X> int submitAndWait(Stream<X> items, Consumer<X> handleItem, Executor executor) {
    Phaser phaser = new Phaser(1); // 1 = register ourselves
    items.forEach(item -> {
            phaser.register(); // new task
            executor.execute(() -> {
                handleItem.accept(item);
                phaser.arrive(); // completed task
            });
    });
    phaser.arriveAndAwaitAdvance(); // block until all tasks are complete
    return phaser.getRegisteredParties()-1; // number of items
}
...
int recognised = submitAndWait(facesInPicture, this::detectFace, executor)

FYI. 仅供参考。 This is fine for singular events but, if this is being called concurrently, a solution involving ForkJoinPool will stop the parent thread from blocking. 这对于单个事件很好,但是,如果同时调用它,涉及ForkJoinPool的解决方案将阻止父线程阻塞。

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

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