1

I have a class with member functions that require a boost::unique_lock be acquired on the respective mutex before performing their operation. However, when the member function is called when there is an existing boost::unique_lock it deadlocks itself.

#include <iostream>
#include "boost/thread.hpp"
#include "boost/foreach.hpp"
#include <list>
class example {
    private:
    std::list< int > int_array;
    std::list< int >::iterator array_iterator;
    mutable boost::shared_mutex array_mutex;
    public:
    /*
     * Move the iterator forward.
     */
    inline example & next( ) {
        boost::unique_lock< boost::shared_mutex > lock( array_mutex );

        array_iterator++;
        if( array_iterator == int_array.end( ) ) {
            array_iterator = int_array.begin( );
        }

        return *this;
    }

    /*
     * Get int_array_mutex.
     */
    inline boost::shared_mutex & mutex( ) const {
        return array_mutex;
    }

    /*
     * Get int_array.
     */
    inline std::list< int > & array() {
        return int_array;
    }
};

int main() {
    example example_instance;

    boost::unique_lock< boost::shared_mutex> lock(example_instance.mutex());

    //Manipulate int_array...
    example_instance.array().push_back(1);
    example_instance.array().push_back(2);
    example_instance.array().push_back(3);
    example_instance.array().push_back(4);
    BOOST_FOREACH(int & x, example_instance.array()) {
        x++;
    }

    //Causes deadlock
    example_instance.next();

    std::cout << "This shall not be emitted." << std::endl;

    return 0;
}

How can I fix this?

Timesquare
  • 399
  • 2
  • 12

2 Answers2

3

I haven't used boost threading, but if I read your code correctly you lock the mutex in your main function then you call the examlpe_instance's next() method which also tries to obtain a lock on the array mutex... naturally that would cause a deadlock, since it doesn't seem like the lock is re-entrant.

Why are you acquiring the lock in the main function?

Update:

You can't re-acquire a lock twice within the same scope, unless you have a re-entrant lock (which you don't), so try changing the scope:

int main() {
    example example_instance;

    {// new scope
        boost::unique_lock< boost::shared_mutex> lock(example_instance.mutex());

        //Manipulate int_array...
        example_instance.array().push_back(1);
        example_instance.array().push_back(2);
        example_instance.array().push_back(3);
        example_instance.array().push_back(4);
        BOOST_FOREACH(int & x, example_instance.array()) {
            x++;
        }
    }// end scope

    // should not cause deadlock now
    example_instance.next();

    std::cout << "This shall not be emitted." << std::endl;

    return 0;
}

If boost works like I imagine it should, then boost should release the lock once it gets out of scope. Try the above modification and see if you still get a deadlock (if you still do, then you have to explicitly release the lock somehow, but I don't know how that's done in boost).

Kiril
  • 39,672
  • 31
  • 167
  • 226
  • I need the lock in the main function so that it can directly manipulate the 'int_array'. I have updated my example to reflect this. – Timesquare Apr 25 '11 at 16:33
  • @Timesquare: see my update. Like I said previously: you can't lock twice within the same scope, unless you have a re-entrant lock (which you don't). So when you try to modify `int_array`, you have to keep that in mind. Does that help? – Kiril Apr 25 '11 at 16:52
-1

What you want is a recurisive_mutex which is more commonly known as a re-entrant mutex. This will allow you to lock the same mutex multiple times within the same thread without causing deadlocks. So to fix your example, just replace all instances of shared_mutex by recursive_mutex and it will work.

The problem is that you can now no longer use shared_lock and upgrade_to_unique_lock since they both require that you have a shared_mutex. As it turns out, according to these two links it looks like shared_mutex are a bad idea anyway. If you really need shared_mutex, you'll have to write your own shared_recursive_mutex because boost doesn't provide one.

Community
  • 1
  • 1
Ze Blob
  • 2,817
  • 22
  • 16
  • 1
    Recursive mutexes are a good path to sloppy coding and should be used rarely. There's no obvious reason for the OP to want to acquire the lock twice, and doing so intentionally is usually an indication that you're breaking the cardinal rule of locks: hold them for as short a time as possible. Any design relying on recursive locks should be examined carefully to determine if there is an alternative, and if not, treated with great caution to ensure no flaws creep in. It's like explicit casting between types, a big red flag that screams "look at me!". – Nicholas Knight Apr 25 '11 at 05:08
  • @Nicholas The problem is that the question doesn't contain enough context to tell whether his situations really needs a re-entrant mutex or not. There are situations where you do need re-entrant mutexes but I agree with you that it's unlikely to be the case here. – Ze Blob Apr 25 '11 at 07:08