5

I'm implementing a concurrent_blocking_queue with minimal functions:

//a thin wrapper over std::queue
template<typename T>
class concurrent_blocking_queue
{
    std::queue<T> m_internal_queue;
     //...
 public:
     void add(T const & item);
     T&   remove();
     bool empty();
};

I intend to use this for producer-consumer problem (I guess, it is where one uses such data structures?). But I'm stuck on one problem which is:

How to elegantly notify consumer when producer is done? How would the producer notify the queue when it is done? By calling a specifiic member function, say done()? Is throwing exception from the queue (i.e from remove function) a good idea?

I came across many examples, but all has infinite loop as if the producer will produce items forever. None discussed the issue of stopping condition, not even the wiki article.

Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • Often you'd use a special value of the type `T`, call it `QuitMessage` or something. But presumably you want to build this into the queue itself, regardless of type, rather than leaving the producer and consumer to define a protocol at a higher level? – Steve Jessop Feb 27 '12 at 12:44
  • @SteveJessop: Yes. I want to make it generic. The idea of special value doesn't seem to be good. The very special value could be a valid value for some `T`, or my feeling says it isn't that good. – Nawaz Feb 27 '12 at 12:46
  • I'm proposing that the producer selects and documents the special value. If they use a value that also has another meaning, then they're probably stupid or something. – Steve Jessop Feb 27 '12 at 12:49
  • @SteveJessop: Why such special value is better than function `done()`, and then throwing exception from `remove()` if `m_done` is `true`? – Nawaz Feb 27 '12 at 12:53
  • You may rewrite your function 'remove' in the following way: bool try_remove(T& item) instead of throwing an exception – Igor Chornous Feb 27 '12 at 12:55
  • Another possibility is to replace the `remove` function with an `InputIterator`. Once the producer has indicated end-of-stream, `operator++` can set the iterator to a value that compares equal to some end-iterator. – Steve Jessop Feb 27 '12 at 12:55
  • curious to know you're doing this for fun or your job work? – Mr.Anubis Feb 27 '12 at 12:55
  • @Nawaz: I'm not going to tell you whether to throw an exception or not. That's down to what kind of interface you want to write, and what kind if interface your consumers want to use. Some people don't like exceptions in non-disaster scenarios. You seem to have some doubts about exceptions, for some reason, so I'm offering alternatives. It's fairly common for messaging protocols to include some kind of stop/quit/end message. – Steve Jessop Feb 27 '12 at 12:56
  • @SteveJessop: The iterator idea seems to be good. Could you elaborate on this a bit more? I mean, how would the producer indicate end-of-stream? – Nawaz Feb 27 '12 at 12:59
  • @Mr.Anubis: It is for my job work which is a fun work as well :D (they're not mutually exclusive). – Nawaz Feb 27 '12 at 13:00
  • @Nawaz: I'd stick with your idea, call `done()` on the queue. And/or you could RAII it - the producer could create an object that represents a write handle on the queue, and whose destructor indicates end-of-stream. – Steve Jessop Feb 27 '12 at 13:00
  • 1
    @Nawaz: *I want to make it generic*, but are you really? Without any other feature in the queue, it is a *generic* tool on which users can build their own protocols, but by adding an specific *end-of-queue* mechanism you are either limiting the use to a single producer, forcing the different producers to agree on an out-of-bounds mechanism to be able to determine when all are done, or complicating the design by forcing producers to register (and potentially introducing race conditions if the last known producer goes away while a new producer is registering). *More* does not always mean better. – David Rodríguez - dribeas Feb 27 '12 at 14:13
  • @DavidRodríguez-dribeas + 1 for difficult-to-pronounce name :) and listing some of the problems of an OOB shutdown without losing flexibility. – Martin James Feb 27 '12 at 14:16
  • ... with the simple solution above it is trivial to implement what you want with a wrapper, by using a more complex data type for the contained element that would mark whether this is the end of the stream, and have the wrapper do what you need. As of what to do, that is more complex, as Steve points out, it depends greately on your domain and the interface that you want to provide... non-throw `try_remove`, or throwing `remove` or a non-throw `remove` that returns a smart pointer that can be null... Consider reading *C++ concurrency in Action* by Anthony Williams, it has advice on interfaces. – David Rodríguez - dribeas Feb 27 '12 at 14:17
  • The approach to set a flag "done" and then throwing an exception if the consumer tries to consume from the queue that is set as "done"/"completed" is what BlockingCollection(T) in C# does. http://msdn.microsoft.com/en-us/library/dd267312.aspx . This is definitely good approach. – Jagannath Feb 27 '12 at 14:18
  • @DavidRodríguez-dribeas: By *generic*, I meant different values for `T`, rather than different kind of producers. – Nawaz Feb 27 '12 at 15:32

4 Answers4

5

I've simply introduced a dummy "done" product in the past. So if the producer can create "products" of, say, type A and type B, I've invented type "done". When a consumer encounters a product of type "done" it knows that further processing isn't required anymore.

foxx1337
  • 1,859
  • 3
  • 19
  • 23
  • "*a product of **type** "done"*"? What does it mean? If you mean a special value, then that special value could be a valid value for some `T`. Say, if `T=int`, what special value would I choose to indicate `done`? You would say some value `N`, then it may be that `N` is one of the values the producer will produce, anyway? – Nawaz Feb 27 '12 at 12:48
  • One may introduce a proxy type M which implements a method bool try_get(T&) and returns false in case if this is the 'end of data' notification – Igor Chornous Feb 27 '12 at 12:57
  • Well, if you have a producer of even ``int``s, then you could produce -1 as a signal to stop. I'd rather produce objects of class type ``Task``, properly defined in the code, and avoid simply generating ``int``s or any other "simple" types. Helps a lot in the long run with debugging, logging and creating more general producers and consumers. – foxx1337 Feb 27 '12 at 13:00
  • A P-C queue for just int's, or other simple scalar types is not often seen - it's inefficient because the effort required to manage the data is out of all proportion to the information transported. Usually, objects, (Task instances, buffers, whatever), are queued and nil/null is always an illegal reference. – Martin James Feb 27 '12 at 14:12
  • 1
    The most frequent pattern I've seen is for the queue to contain pointers to polymorphic objects, with a virtual function `processRequest`. And additional derived type to handle `done` does the trick well **as long as there is only one consumer**. If there are more than one consumer, some additional logic is needed to ensure that all consumers are notified. – James Kanze Feb 27 '12 at 14:46
  • 1
    @JamesKanze - the easiest way round multiple consumers is to push on a NULL - a NULL cannot leak. Each consumer can pop the NULL, recognise it as such, push it back onto the queue and terminate. A similar approach using a 'done' polymorphic task is possible if the queue keeps a running count of outstanding consumers, (the count goes into the task and gets decremented on every processRequest until 0, then the last thread notifies and deletes the task), or the leak of a task can be tolerated. – Martin James Feb 27 '12 at 15:03
  • 1
    @MartinJames Having the consumer repush any termination signal it receives is a good idea that hadn't occurred to me. With regards to a possible leak---just ensure that the destructor for the queue itself ensures that all remaining elements are correctly disposed of. (There's still a possible problem in knowing when you can destruct the queue, of course.) – James Kanze Feb 27 '12 at 15:44
  • @JamesKanze - probably best approach is for the last thread that gets the termination-task to dispose the termination-task and the queue, then terminate itself. This allows the queue, and all its consumers, to auto-destruct without any further intervention or monitoring. This implies some way for the last thread to know it's the last thread - OK if a thread pool with a work-thread count, but bad in the case of a general P-C queue. – Martin James Feb 27 '12 at 17:19
  • @MartinJames The best solution probably depends on context. If some master thread has set things up, to distribute work between cores, then it probably also knows when everything has finished, and can take charge of deleting (if necessary---I've often used local variables for the queue). In other contexts, I don't know; I've less experience with them. – James Kanze Feb 27 '12 at 18:21
2

It is true that it's common to enqueue a special "we're done" message; however I think OP's original desire for an out-of-band indicator is reasonable. Look at the complexity people are contemplating to set up an in-band completion message! Proxy types, templating; good grief. I'd say a done() method is simpler and easier, and it makes the common case (we're not done yet) faster and cleaner.

I would agree with kids_fox that a try_remove that returns an error code if the queue is done is preferred, but that's stylistic and YMMV.

Edit: Bonus points for implementing a queue that keeps track of how many producers are remaining in a multiple-producers situation and raises the done exception iff all producers have thrown in the towel ;-) Not going to do that with in-band messages!

zmccord
  • 2,398
  • 1
  • 18
  • 14
  • I disagree. Just to start with, how could a queue keep a track of how many producers are remaining - the queue does not know how many producers there are in a system. – Martin James Feb 27 '12 at 13:47
  • @MartinJames: The queue can be implemented that way so it keep track of producers? Something like `queue.register_producer(this)` and then `queue.unregister_producer(this)`? – Nawaz Feb 27 '12 at 14:06
  • @Nawaz - yes, this is possible, but limits flexibility and impacts encapsulation - it means that the producer has to have direct access to the queue class, something that is often not desired and/or complex to arrange. What happens if a producer does not log in, or logs in more than once? If I register with the queue and later do not wish to continue, do I have to unregister and, if so, do I have to ensure all my outstanding objects are processed? – Martin James Feb 27 '12 at 14:26
  • I'm not sure how you could possibly have producer/consumer without the producer being able to access the queue class. That's… what the queue class is for. I was thinking of a register/unregister scheme as Nawaz suggests. If a producer doesn't log in, the programmer messed up. If they do it more than once, the programmer messed up. If you want to leave, you have to unregister. Whether you have to ensure your outstanding objects are processed is dependent on the workload at hand, but there's no reason to in the general case. I suggested this as a thought experiment… I think it's gone OT. – zmccord Feb 27 '12 at 15:48
1

'Stopping' is not often discussed because it's often never done. In those cases where it is required, it's often just as easier and more flexible to enqueue a poison-pill using the higher-level P-C protocol itself as it is to build extra functionality into the queue itself.

If you really want to do this, you could indeed set a flag that causes every consumer to raise an exception, either 'immediately' or whenever it gets back to the queue, but there are problems. Do you need the 'done' method to be synchronous, ie. do you want all the consumers gone by the time 'done' returns, or asynchronous, ie. the last consumer thread calls an event parameter when all the other the consumers are gone?

How are you going to arrange for those consumers that are currently waiting to wake up? How many are waiting and how many are busy, but will return to the queue when they have done their work? What if one or more consumers are stuck on a blocking call, (perhaps they can be unblocked, but that requires a call from another thread - how are you going to do that)?

How are the consumers going to notify that they have handled their exception and are about to die? Is 'about to die' enough, or do you need to wait on the thread handle? If you have to wait on the thread handle, what is going to do the waiting - the thread requesting the queue shutdown or the last consumer thread to notify?

Oh yes - to be safe, you should arrange for producer threads that turn up with objects to queue up while in 'shutting down' state to raise an exception as well.

I raise these questions becasue I've done all this once, a long time ago. Eventually, it all worked-ish. The objects queued up all had to have a 'QueuedItem' inserted into their inheritance chain, (so that a job-cancellation method could be exposed to the queue), and the queue had to keep a thread-safe list of objects that had been popped-off by threads but not processed yet.

After a while, I stopped using the class in favour of a simple P-C queue with no special shutdown mechanism.

Martin James
  • 24,453
  • 3
  • 36
  • 60
  • The point about waking up consumers that are already waiting is a good one; simplest would be to increment the semaphore (I presume) that's protecting the queue. OP was about single-consumer single-producer, so that's sufficient. Most of the complexity you discuss is with respect to making done() synchronous, though, so if that's not necessary then the class isn't bad to write. – zmccord Feb 27 '12 at 13:43
  • Actually, maybe we interpreted the original post differently. I was thinking OP wanted to tell consumers that no more data would ever come if they tried to remove from the queue after the last item had been removed. I gather you read the OP as wanting a way for the queue to tell consumers to terminate processing early before processing all the extant queue objects? The latter sounds more difficult to clean up. – zmccord Feb 27 '12 at 13:49
  • @zmccord - the OP post was a little unclear. 'single-consumer single-producer' - yes, OK, but such queues are not very useful/flexible and somewhat unsafe - no matter how much documentation you put in, some developers will eventually shove on a message from another thread. 'Real' P-C queues are unconditionally safe. If the 'done' does not need to be synchronous and no completion notification is required then, yes, just shove a null/nil/whatever illegal-object on and have each consumer thread pop it off, recognise it, push it back on and then die. – Martin James Feb 27 '12 at 14:06
1

My queues have usually used pointers (with an std::auto_ptr in the interface, to clearly indicate that the sender may no longer access the pointer); for the most part, the queued objects were polymorphic, so dynamic allocation and reference semantics were required anyway. Otherwise, it shouldn't be too difficult to add an “end of file” flag to the queue. You'd need a special function on the producer side (close?) to set it (using exactly the same locking primitives as when you write to the queue), and the loop in the removal function must wait for either something to be there, or the queue to be closed. Of course, you'll need to return a Fallible value, so that the reader can know whether the read succeeded or not. Also, don't forget that in this case, you need a notify_all to ensure that all processes waiting on the condition are awoken.

BTW: I don't quite see how your interface is implementable. What does the T& returned by remove refer to. Basically, remove has to be something like:

Fallible<T>
MessageQueue<T>::receive()
{
    ScopedLock l( myMutex );
    while ( myQueue.empty() && ! myIsDone )
        myCondition.wait( myMutex );
    Fallible<T> results;
    if ( !myQueue.empty() ) {
        results.validate( myQueue.top() );
        myQueue.pop();
    }
    return results;
}

Even without the myIsDone condition, you have to read the value into a local variable before removing it from the queue, and you can't return a reference to a local variable.

For the rest:

void
MessageQueue<T>::send( T const& newValue )
{
    ScopedLock l( myMutex );
    myQueue.push( newValue );
    myCondition.notify_all();
}

void
MessageQueue<T>::close()
{
    ScopedLock l( myMutex );
    myIsDone = true;
    myCondition.notify_all();
}
James Kanze
  • 150,581
  • 18
  • 184
  • 329
  • In your `receive()` implementation, isn't the `while` loop blocked forever, as the mutex will never be acquired in `send` (or `close`) because it is already acquired by `receive` which didn't release it *before* going to sleep? – Nawaz Feb 28 '12 at 11:25
  • @Nawaz It calls `myCond.wait()`, which unblocks it. In fact, there's a probable error, or at least something misleading, in the code, since all of the `wait()` functions on a condition that I know of take a mutex as argument as well. The whole point being that you have to hold the mutex until you're actually waiting, to avoid any other process intervening between the moment you release the mutex and the moment you start waiting. `myCond.wait()` should atomically release the mutex and cause you to wait. I'll edit the response to reflect this. – James Kanze Feb 28 '12 at 13:49
  • I didn't understand the rationale why you said the `remove` function should return `Fallible` rather than `T&`? What is `Fallible`? – Nawaz Feb 29 '12 at 09:10
  • The `remove` function needs to return when the producer has shut down, and there is no `T` object to refer to. `Fallible` is a class template from Barton and Nackman, which supports returning a value which might be missing; it's one of those classes which should be in every toolkit (perhaps under a different name---the name `Fallible` refers to the use Barton and Nackman made of it, as a return value which isn't guaranteed; as an argument, `Optional` might be better, and generically, `Maybe`). – James Kanze Feb 29 '12 at 11:42
  • If the producer shutdown, then I use poison object, a specific object, to notify end-of-stream. If the consumer gets this, then consumer should stop calling `remove` in the first place, isn't? Also, if it still calls, then it will block forever, as there is nobody to notify end-of-stream anymore. – Nawaz Feb 29 '12 at 11:49
  • If you use a poison object (a done command), then you don't need any special logic to notify the queue that you are done. I mentioned this solution at the start of my answer. From your question, however, I supposed that you wanted something else; and a truly generic queue must use some other mechanism, since not all types will have a sentinel value which can be used. – James Kanze Feb 29 '12 at 11:59
  • Yes. That is true. But with that I've another problem: for how long the consumer will be blocked in `remove` if producer doesn't notify it by adding a poison object to the queue? – Nawaz Feb 29 '12 at 12:05
  • Until `isDone` is true. `MessageQueue::close()` does a `myCond.notify_all()`, which wakes up any threads which are waiting, and `MessageQueue::receive` checks for `isDone` in its loop. – James Kanze Feb 29 '12 at 12:08