Why boost::recursive_mutex is not working as expected?

Kjir picture Kjir · Apr 2, 2010 · Viewed 7.6k times · Source

I have a custom class that uses boost mutexes and locks like this (only relevant parts):

template<class T> class FFTBuf
{
    public:
        FFTBuf(); 
        [...]
        void lock();
        void unlock();
    private:
        T *_dst;
        int _siglen;
        int _processed_sums;
        int _expected_sums;
        int _assigned_sources;
        bool _written;
        boost::recursive_mutex _mut;
        boost::unique_lock<boost::recursive_mutex> _lock;
};

template<class T> FFTBuf<T>::FFTBuf() : _dst(NULL), _siglen(0),
    _expected_sums(1), _processed_sums(0), _assigned_sources(0),
    _written(false), _lock(_mut, boost::defer_lock_t())
{
}

template<class T> void FFTBuf<T>::lock()
{
    std::cerr << "Locking" << std::endl;
    _lock.lock();
    std::cerr << "Locked" << std::endl;
}

template<class T> void FFTBuf<T>::unlock()
{
    std::cerr << "Unlocking" << std::endl;
    _lock.unlock();
}

If I try to lock more than once the object from the same thread, I get an exception (lock_error):

#include "fft_buf.hpp"

int main( void ) {
    FFTBuf<int> b( 256 );
    b.lock();
    b.lock();
    b.unlock();
    b.unlock();

    return 0;
}

This is the output:

sb@dex $ ./src/test
Locking
Locked
Locking
terminate called after throwing an instance of 'boost::lock_error'
   what(): boost::lock_error
zsh: abort    ./src/test

Why is this happening? Am I understanding some concept incorrectly?

Answer

Matthieu M. picture Matthieu M. · Apr 2, 2010

As the name implies, the Mutex is recursive but the Lock is not.

That said, you have here a design problem. The locking operations would be better off not being accessible from the outside.

class SynchronizedInt
{
public:
  explicit SynchronizedInt(int i = 0): mData(i) {}

  int get() const
  {
    lock_type lock(mMutex);
    toolbox::ignore_unused_variable_warning(lock);

    return mData;
  }

  void set(int i)
  {
    lock_type lock(mMutex);
    toolbox::ignore_unused_variable_warning(lock);

    mData = i;
  }


private:
  typedef boost::recursive_mutex mutex_type;
  typedef boost::unique_lock<mutex_type> lock_type;

  int mData;
  mutable mutex_type mMutex;
};

The main point of the recursive_mutex is to allow chain locking in a given thread which may occur if you have complex operations that call each others in some case.

For example, let's add tweak get:

int SynchronizedInt::UnitializedValue = -1;

int SynchronizedInt::get() const
{
  lock_type lock(mMutex);
  if (mData == UnitializedValue) this->fetchFromCache();
  return mData;
}

void SynchronizedInt::fetchFromCache()
{
  this->set(this->fetchFromCacheImpl());
}

Where is the problem here ?

  • get acquires the lock on mMutex
  • it calls fetchFromCache which calls set
  • set attempts to acquire the lock...

If we did not have a recursive_mutex, this would fail.