简体   繁体   English

阻塞队列和多线程消费者,如何知道何时停止

[英]Blocking queue and multi-threaded consumer, how to know when to stop

I have a single thread producer which creates some task objects which are then added into an ArrayBlockingQueue (which is of fixed size). 我有一个单线程生成器,它创建一些任务对象,然后将其添加到ArrayBlockingQueue (具有固定大小)。

I also start a multi-threaded consumer. 我也开始了一个多线程的消费者。 This is build as a fixed thread pool ( Executors.newFixedThreadPool(threadCount); ). 这是构建为固定线程池( Executors.newFixedThreadPool(threadCount); )。 I then submit some ConsumerWorker intances to this threadPool, each ConsumerWorker having a refference to the above mentioned ArrayBlockingQueue instance. 然后我向这个threadPool提交了一些ConsumerWorker入口,每个ConsumerWorker都对上面提到的ArrayBlockingQueue实例进行了引用。

Each such Worker will do a take() on the queue and deal with the task. 每个这样的Worker都会对队列执行take()并处理任务。

My issue is, what's the best way to have a Worker know when there won't be any more work to be done. 我的问题是,当没有更多的工作要做时,让工人知道的最佳方法是什么。 In other words, how do I tell the Workers that the producer has finished adding to the queue, and from this point on, each worker should stop when he sees that the Queue is empty. 换句话说,如何告诉Workers,生产者已经完成了对队列的添加,从这一点开始,每个工作人员在看到Queue为空时应该停止。

What I've got now is a setup where my Producer is initialized with a callback which is triggered when he finishes it's job (of adding stuff to the queue). 我现在得到的是一个设置,我的Producer初始化了一个回调,当他完成它的工作(向队列中添加东西)时会触发回调。 I also keep a list of all the ConsumerWorkers I've created and submitted to the ThreadPool. 我还保留了我创建并提交给ThreadPool的所有ConsumerWorkers的列表。 When the Producer Callback tells me that the producer is done, I can tell this to each of the workers. 当Producer Callback告诉我生产者已完成时,我可以告诉每个工人。 At this point they should simply keep checking if the queue is not empty, and when it becomes empty they should stop, thus allowing me to gracefully shutDown the ExecutorService thread pool. 此时,他们应该只是继续检查队列是否为空,当它变为空时它们应该停止,从而允许我优雅地关闭ExecutorService线程池。 It's something like this 就是这样的

public class ConsumerWorker implements Runnable{

private BlockingQueue<Produced> inputQueue;
private volatile boolean isRunning = true;

public ConsumerWorker(BlockingQueue<Produced> inputQueue) {
    this.inputQueue = inputQueue;
}

@Override
public void run() {
    //worker loop keeps taking en element from the queue as long as the producer is still running or as 
    //long as the queue is not empty:
    while(isRunning || !inputQueue.isEmpty()) {
        System.out.println("Consumer "+Thread.currentThread().getName()+" START");
        try {
            Object queueElement = inputQueue.take();
            //process queueElement
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

//this is used to signal from the main thread that he producer has finished adding stuff to the queue
public void setRunning(boolean isRunning) {
    this.isRunning = isRunning;
}

} }

The problem here is that I have an obvious race condition where sometimes the producer will finish, signal it, and the ConsumerWorkers will stop BEFORE consuming everything in the queue. 这里的问题是我有一个明显的竞争条件,有时生产者将完成,发出信号,消费者工作者将在消耗队列中的所有内容之前停止。

My question is what's the best way to synchronize this so that it all works ok? 我的问题是,同步这个的最佳方法是什么,以便一切正常? Should I synchronize the whole part where it checks if the producer is running plus if the queue is empty plus take something from the queue in one block (on the queue object)? 我是否应该同步整个部分来检查生产者是否正在运行加上如果队列是空的加上从队列中取出一些块(在队列对象上)? Should I just synchronize the update of the isRunning boolean on the ConsumerWorker instance? 我应该只在ConsumerWorker实例上同步isRunning布尔的更新吗? Any other suggestion? 还有其他建议吗?

UPDATE, HERE'S THE WORKING IMPLEMENTATION THAT I'VE ENDED UP USING: 更新,这里是我最终使用的工作实现:

public class ConsumerWorker implements Runnable{

private BlockingQueue<Produced> inputQueue;

private final static Produced POISON = new Produced(-1); 

public ConsumerWorker(BlockingQueue<Produced> inputQueue) {
    this.inputQueue = inputQueue;
}

@Override
public void run() {
    //worker loop keeps taking en element from the queue as long as the producer is still running or as 
    //long as the queue is not empty:
    while(true) {
        System.out.println("Consumer "+Thread.currentThread().getName()+" START");
        try {
            Produced queueElement = inputQueue.take();
            Thread.sleep(new Random().nextInt(100));
            if(queueElement==POISON) {
                break;
            }
            //process queueElement
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("Consumer "+Thread.currentThread().getName()+" END");
    }
}

//this is used to signal from the main thread that he producer has finished adding stuff to the queue
public void stopRunning() {
    try {
        inputQueue.put(POISON);
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

} }

This was inspired heavily by JohnVint's answer below, with only some minor modifications. 这很大程度上受到了JohnVint在下面的回答的启发,只有一些小修改。

=== Update due to @vendhan's comment. ===由于@ vendhan的评论而更新。

Thank you for your obeservation. 谢谢你的观察。 You are right, the first snippet of code in this question has (amongst other issues) the one where the while(isRunning || !inputQueue.isEmpty()) doesn't really make sense. 你是对的,这个问题中的第一个代码片段(在其他问题中)是while(isRunning || !inputQueue.isEmpty())没有意义的while(isRunning || !inputQueue.isEmpty())

In my actual final implementation of this, I do something which is closer to your suggestion of replacing "||" 在我实际的最终实现中,我做了一些更接近你更换“||”的建议。 (or) with "&&" (and), in the sense that each worker (consumer) now only checks if the element he's got from the list is a poison pill, and if so stops (so theoretically we can say that the worker has to be running AND the queue must not be empty). (或)用“&&”(和),从某种意义上说,每个工人(消费者)现在只检查他从列表中得到的元素是否是毒丸,如果是这样,那么理论上我们可以说工人有要运行并且队列不能为空)。

You should continue to take() from the queue. 你应该继续从队列中take() You can use a poison pill to tell the worker to stop. 你可以使用毒丸告诉工人停止。 For example: 例如:

private final Object POISON_PILL = new Object();

@Override
public void run() {
    //worker loop keeps taking en element from the queue as long as the producer is still running or as 
    //long as the queue is not empty:
    while(isRunning) {
        System.out.println("Consumer "+Thread.currentThread().getName()+" START");
        try {
            Object queueElement = inputQueue.take();
            if(queueElement == POISON_PILL) {
                 inputQueue.add(POISON_PILL);//notify other threads to stop
                 return;
            }
            //process queueElement
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

//this is used to signal from the main thread that he producer has finished adding stuff to the queue
public void finish() {
    //you can also clear here if you wanted
    isRunning = false;
    inputQueue.add(POISON_PILL);
}

I'd send the workers a special work packet to signal that they should shut down: 我会给工人们发一个特殊的工作包来表示他们应该关闭:

public class ConsumerWorker implements Runnable{

private static final Produced DONE = new Produced();

private BlockingQueue<Produced> inputQueue;

public ConsumerWorker(BlockingQueue<Produced> inputQueue) {
    this.inputQueue = inputQueue;
}

@Override
public void run() {
    for (;;) {
        try {
            Produced item = inputQueue.take();
            if (item == DONE) {
                inputQueue.add(item); // keep in the queue so all workers stop
                break;
            }
            // process `item`
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

} }

To stop the workers, simply add ConsumerWorker.DONE to the queue. 要停止worker,只需将ConsumerWorker.DONE添加到队列中即可。

Can not we do it using a CountDownLatch , where the size is the number of records in the producer. 我们不能使用CountDownLatch实现它,其中size是生产者中的记录数。 And every consumer will countDown after process a record. 并且每个消费者都会在处理countDown后记录下来。 And its crosses the awaits() method when all tasks finished. 当所有任务完成时,它会跨越awaits()方法。 Then stop all ur consumers. 然后停止所有你的消费者。 As all records are processed. 随着所有记录的处理。

In your code-block where you attempt to retrive element from the queue , use poll(time,unit) instead of the take() . 在您尝试从队列中检索元素的代码块中,使用poll(time,unit)而不是take()

try { 
    Object queueElement = inputQueue.poll(timeout,unit);
     //process queueElement        
 } catch (InterruptedException e) {
        if(!isRunning && queue.isEmpty())
         return ; 
 } 

By specifying appropriate values of timeout , you ensure that threads wont keep blocking in case there is a unfortunate sequence of 通过指定适当的超时值,可以确保线程不会阻塞,以防有一个不幸的序列

  1. isRunning is true isRunning是真的
  2. Queue becomes empty , so threads enter blocked wait ( if using take() 队列变空,因此线程进入阻塞等待(如果使用take()
  3. isRunning is set to false isRunning设置为false

I had to use a multi-threaded producer and a multi-threaded consumer. 我不得不使用多线程生产者和多线程消费者。 I ended up with a Scheduler -- N Producers -- M Consumers scheme, each two communicate via a queue (two queues total). 我最终得到了一个Scheduler -- N Producers -- M Consumers方案,每个都通过队列进行通信(总共两个队列)。 The Scheduler fills the first queue with requests to produce data, and then fills it with N "poison pills". 调度程序使用生成数据的请求填充第一个队列,然后用N“毒丸”填充它。 There is a counter of active producers (atomic int), and the last producer that receives the last poison pill sends M poison pills to the consumer queue. 有一个活跃的生产者计数器(原子int),最后一个接收最后一个毒丸的生产者将M毒丸送到消费者队列。

There are a number of strategies you could use, but one simple one is to have a subclass of task that signals the end of the job. 您可以使用许多策略,但一个简单的策略是使用任务的子类来表示作业的结束。 The producer doesn't send this signal directly. 制作人不直接发送此信号。 Instead, it enqueues an instance of this task subclass. 相反,它将此任务子类的实例排入队列。 When one of your consumers pulls off this task and executes it, that causes the signal to be sent. 当您的某个消费者完成此任务并执行它时,会导致信号被发送。

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

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