0

I want to implement a producer / consumer scenario where i have multiple producers and a single consumer. Producers keep adding items to a queue and consumer dequeues the items. When the consumer has processed enough items, both the producers and consumer should stop execution. Consumer can easily terminate itself when it process enough items. But the producers should also know when to exit. The typical producer poison pills do not work here.

One way to do it would be to have a shared boolean variable between consumer and producers. Consumer sets the boolean variable to true and producers periodically check the variable and exit if it set to true.

Any better ideas on how i can do this ?

shadowfax
  • 971
  • 1
  • 10
  • 17
  • Could you elaborate on "has processed enough items". Are we talking about a fixed number? Is this known in advance? – raupach Jun 18 '13 at 11:52
  • In a multi-threaded scenario, it is very likely that the producers will produce more items than the consumer needs, until the consumer sends out the message that it doesn't need anymore. Is that okay, or do you need a scenario where the producers should poll something that keeps track of the overall requriement, before each 'produce' attempt? – Rajesh J Advani Jun 18 '13 at 11:53
  • It is not a fixed number. The consumer dynamically determines when it is done. – shadowfax Jun 18 '13 at 11:55
  • @RajeshJAdvani The producer needs to poll if the consumer has received enough before every produce attempt. – shadowfax Jun 18 '13 at 11:56
  • That breaks the separation between producer and consumer. If you don't care about overproduction, then a shared resource for notification is fine. Otherwise, the tracking needs to be done outside the consumer, and use by both producers as well as consumers. – Rajesh J Advani Jun 18 '13 at 12:04
  • Have the producer threads check the size of the concurrent queue. When the queue gets to be a certain size, they stop processing. This allows the consumer thread to finish it's work. – Gilbert Le Blanc Jun 18 '13 at 12:05
  • @GilbertLeBlanc That operation is not atomic. – John Vint Jun 18 '13 at 12:25

4 Answers4

4

I suppose you can have a shared counter and have a max. If an increment is greater than the max value then the thread cannot add to the queue.

private final AtomicInteger count = new AtomicInteger(0);
private final int MAX = ...;/
private final BlockingQueue<T> queue = ...;
public boolean add(T t){
    if(count.incrementAndGet() > MAX) 
           return false;

    return queue.offer(t);
}
John Vint
  • 39,695
  • 7
  • 78
  • 108
0

Not sure if this approach would be any use.

  • Include a reference to the producer in the message.
  • Producer provides a call back method to tell them to stop producing.
  • Consumer keeps a registry of producers based on the unique set of references that are passed to it.
  • When the consumer has had enough, it iterates over the registry of producers, and tells them to stop by calling the callback method.

Would only work if producer and consumer are in the same JVM Wouldn't stop any new producers from starting up

And I'm not sure it maintains the separation of producer and consumer

Alternatively, as the Queue is the shared resource between these two objects, could you introduce an "isOpen" state on the queue which is checked before the producer writes to it and is set by the consumer when it has done as much work as it is happy to do?

DaveH
  • 7,187
  • 5
  • 32
  • 53
0

From what I understand you'll need something like this:

private static final BlockingQueue<String> queue = new LinkedBlockingQueue<String>();

private static boolean needMore = true;

static class Consumer implements Runnable
{
    Scanner scanner = new Scanner(System.in);

    @Override
    public void run()
    {           
        do
        {
            try
            {
                String s = queue.take();

                System.out.println("Got " + s);

                needMore = scanner.nextBoolean();
            } catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }
        while (needMore);
    }       
}

static class Producer implements Runnable
{
    Random rand = new Random();

    @Override
    public void run()
    {
        System.out.println("Starting new producer...");

        do
        {
            queue.add(String.valueOf(rand.nextInt()));

            try
            {
                Thread.sleep(1000);
            } catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }
        while (needMore);

        System.out.println("Producer shuts down.");
    }       
}

public static void main(String[] args) throws Exception
{        
    Thread producer1 = new Thread(new Producer());
    Thread producer2 = new Thread(new Producer());
    Thread producer3 = new Thread(new Producer());

    Thread consumer = new Thread(new Consumer());

    producer1.start();
    producer2.start();
    producer3.start();

    consumer.start();

    producer1.join();
    producer2.join();
    producer3.join();
    consumer.join();

    return;
}

The consumer dynamically decides if it needs more data and stops when it has found what it was searching for example; this is simulated by the user inputting true/false for continuing/stopping.

Here is an I/O sample:

Starting new producer...
Starting new producer...
Starting new producer...
Got -1782802247
true
Got 314306979
true
Got -1787470224
true
Got 1035850909
false
Producer shuts down.
Producer shuts down.
Producer shuts down.
Pragmateek
  • 13,174
  • 9
  • 74
  • 108
0

This may not look clean on first sight, but I think it's actually cleaner than having an extra variable etc. if you are trying to do this as a part of shutdown process.

Make your consumers an ExecutorService, and from your consumer task, call shutdownNow() when the task decides that the consumers had consumed enough. This will cancel all pending tasks on the queue, interrupt currently running tasks and the producers will start to get RejectedExecutionException upon submission. You can treat this exception as a signal from the consumers.

Only caveat is that when you have multiple consumers, calling shutdownNow() in a serial manner will not guarantee that no task will be executed after one consumer decided it was enough. I'm assuming that's fine. If you need this guarantee, then you can indeed share an AtomicBoolean and let all producers and consumers check it.

Enno Shioji
  • 26,542
  • 13
  • 70
  • 109