views:

192

answers:

3

I am a newcomer to the Boost library, and am trying to implement a simple producer and consumer threads that operate on a shared queue. My example implementation looks like this:

#include <iostream>
#include <deque>
#include <boost/thread.hpp>

boost::mutex mutex;
std::deque<std::string> queue;

void producer() 
{
    while (true) {
        boost::lock_guard<boost::mutex> lock(mutex);

        std::cout << "producer() pushing string onto queue" << std::endl;

        queue.push_back(std::string("test"));
    }
}

void consumer()
{
    while (true) {
        boost::lock_guard<boost::mutex> lock(mutex);

        if (!queue.empty()) {
            std::cout << "consumer() popped string " << queue.front() << " from queue" << std::endl;

            queue.pop_front();
        }
    }
}

int main()
{
    boost::thread producer_thread(producer);
    boost::thread consumer_thread(consumer);

    sleep(5);

    producer_thread.detach();
    consumer_thread.detach();

    return 0;
}

This code runs as I expect, but when main exits, I get

/usr/include/boost/thread/pthread/mutex.hpp:45:    
    boost::mutex::~mutex(): Assertion `!pthread_mutex_destroy(&m)' failed.
consumer() popped string test from queue
Aborted

(I'm not sure if the output from consumer is relevant in that position, but I've left it in.)

Am I doing something wrong in my usage of Boost?

+3  A: 

You give your threads (producer & consumer) the mutex object and then detach them. They are supposed to run forever. Then you exit from your program and the mutex object is no longer valid. Nevertheless your threads still try to use it, they don't know that it is no longer valid. If you had used the NDEBUG define you would have got a coredump.

Are you trying to write a daemon application and this is the reason for detaching threads?

skwllsp
I'm not trying to write any particular type of app—I'm just trying to get familiar with the Boost.Thread library. The calls to `detach` were put in during a quick debug session; the behaviour is identical if I remove them. I initially suspected that the threads needed to be stopped before the program exited, hence the `detach` call.
r_
Don't exit from the main thread immediately and don't detach consumer and producer. Wait in the main thread until your consumer and producer work. And when they are finished join them. And then exit from main.
skwllsp
That makes sense, and does stop the assertion, thanks!
r_
+2  A: 

A bit off-topic but relevant imo (...waits for flames in comments).

The consumer model here is very greedy, looping and checking for data on the queue continually. It will be more efficient (waste less CPU cycles) if you have your consumer threads awakened determistically when data is available, using inter-thread signalling rather than this lock-and-peek loop. Think about it this way: while the queue is empty, this is essentially a tight loop only broken by the need to acquire the lock. Not ideal?

void consumer()
{
    while (true) {
        boost::lock_guard<boost::mutex> lock(mutex);

        if (!queue.empty()) {
            std::cout << "consumer() popped string " << queue.front() << " from queue" << std::endl;

            queue.pop_front();
        }
    }
}

I understand that you are learning but I would not advise use of this in 'real' code. For learning the library though, it's fine. To your credit, this is a more complex example than necessary to understand how to use the lock_guard, so you are aiming high!

Eventually you will most likely build (or better if available, reuse) code for a queue that signals workers when they are required to do work, and you will then use the lock_guard inside your worker threads to mediate accesses to shared data.

Steve Townsend
Off-topic, but certainly useful information, thanks! I agree that this is a very greedy consumer, but this stemmed from an earlier test in which there was no synchronisation and I was trying to encourage the program to lock up :)
r_
A: 

When main exits, all the global objects are destroyed. Your threads, however, do continue to run. You therefore end up with problems because the threads are accessing a deleted object.

Bottom line is that you must terminate the threads before exiting. The only what to do this though is to get the main program to wait (by using a boost::thread::join) until the threads have finished running. You may want to provide some way of signaling the threads to finish running to save from waiting too long.

The other issue is that your consumer thread continues to run even when there is not data. You might want to wait on a boost::condition_variable until signaled that there is new data.

doron