I have a class called ‘subscribedQueue’. This class receives its data via its subscribed publishers (plural) calling its push method.
In another thread, this class’s pop method is called to receive that data.
Thus in a sense this class is a kind of buffer between multiple publishers and their subscribers. For the implementation I have based myself on information found about thread-safe queues here.
Now my question is twofold:
- If I would use the same mutex for pusing and popping values (currently I’m using two differrent mutexes), is it possible for my program to get stuck, waiting on a blocked push?
- If not, how is it possible that both the push and pop methods can get past ‘lock(the_same_mutex)’.
My assumption is that, if I would use the same mutex and the program enters the pop method, it will acquire the lock in pop, check if the queue is empty and wait on the condition variable which can never be set in the push method (as the lock is already acquired by pop).
current Code (using two different mutexes):
#include <boost/thread.hpp>
#include <queue>
#include "subscriber.h"
#include "pubdata.h"
#ifdef DEBUG
#include <iostream>
#include <boost/lexical_cast.hpp>
#endif
namespace PUBLISHSUBSCRIBE
{
template<class T>
class SubscribedQueue: public PUBLISHSUBSCRIBE::Subscriber<T>, private std::queue< PubData<T> >
{
public:
PubData<T> pop(); //removes the next item from the queue, blocks until the queue is not empty
void push(const PubData<T> data); //method used by the publisher to push data onto the queue
private:
mutable boost::mutex writeMutex_; //only needed for publishing/pushing data
mutable boost::mutex readMutex_; //only needed for reading/popping data
boost::condition_variable notify_;
};
template<class T>
PubData<T> SubscribedQueue<T>::pop() { //Blocks until the queue is not empty
boost::mutex::scoped_lock lock(readMutex_);
while(std::queue< PubData<T> >::empty())
notify_.wait(lock); //block until recieving a notification AND the queue is not empty
PubData<T> head = std::queue< PubData<T> >::front();
std::queue< PubData<T> >::pop();
#ifdef DEBUG
std::string debugOut("pop: " + boost::lexical_cast<std::string>(head) + " - timestamp: " + boost::lexical_cast<std::string>(head.timestamp()) + " - from: " + boost::lexical_cast<std::string>(this) + "\n" );
std::cout <<debugOut;
#endif
lock.unlock();
return head;
}
template<class T>
void SubscribedQueue<T>::push(const PubData<T> data){
boost::mutex::scoped_lock lock(writeMutex_);
#ifdef DEBUG
std::cout << "published: " << data << std::endl;
#endif
std::queue< PubData<T> >::push(data);
lock.unlock();
notify_.notify_one();
}
}
#endif //SUBSCRIBEDQUEUE_H
[edit] what worries me most is this: I have a boost::condition_variable notify_ on which a ‘wait until notified’ is performed in pop.
But pop must first lock the mutex, the same mutex that must also be locked in ‘push’ in order to ‘notify’ the condition variable.
So won’t cause this a deadlock, and why not?
Standard library containers aren’t thread safe; If you attempt to modify a container from multiple threads simultaneously then bad things happen.
If you have a separate mutex for push and pop operations then you don’t protect against a simultaneous push and pop from two threads, so you haven’t really protected the collection at all.
When you wait on the condition variable in pop, wait() unlocks the mutex, so a push() while waiting will be able to lock it. push() calls notify_one() and unlocks the mutex by virtue of the scoped_lock going out of scope at the end of the function. Then, when the pop() thread is next scheduled, it will immediately re-lock the mutex and proceed.