为什么boost :: recursive_mutex没有按预期工作?

时间:2010-04-02 13:36:40

标签: c++ boost locking mutex recursive-mutex

我有一个使用boost互斥锁和这样的锁定的自定义类(只有相关部分):

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();
}

如果我尝试从同一个线程多次锁定对象,我会得到一个异常(lock_error):

#include "fft_buf.hpp"

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

    return 0;
}

这是输出:

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

为什么会这样?我错误地理解了一些概念吗?

3 个答案:

答案 0 :(得分:4)

顾名思义,互斥锁是recursive但锁定不是。

那就是说,你有一个设计问题。锁定操作最好不能从外部访问。

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;
};

recursive_mutex的要点是允许在给定线程中进行链锁定,如果你有复杂的操作在某些情况下相互调用,可能会发生这种情况。

例如,让我们添加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());
}

这里的问题在哪里?

  • get获取mMutex
  • 上的锁定
  • 会调用fetchFromCache来调用set
  • set试图获得锁定......

如果我们没有recursive_mutex,则会失败。

答案 1 :(得分:3)

锁定不应该是受保护的资源的一部分,而应该是调用者的一部分,因为您有一个调用者通过线程。他们必须使用不同的unique_lock。

unique_lock的目的是使用RAII锁定和释放互斥锁,因此您无需显式调用unlock。

当在方法体内声明unique_lock时,它将属于调用线程堆栈。

因此更正确的用法是:

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

template<class T>
class FFTBuf
{
public :
    FFTBuf()
    {
    }

    // this can be called by any thread
    void exemple() const
    {
        boost::recursive_mutex::scoped_lock lock( mut );
        std::cerr << "Locked" << std::endl;

        // we are safe here
        std::cout << "exemple" << std::endl ;

        std::cerr << "Unlocking ( by RAII)" << std::endl;
    }

    // this is mutable to allow lock of const FFTBuf
    mutable boost::recursive_mutex mut;
};    

int main( void )
{
    FFTBuf< int > b ;

    {
        boost::recursive_mutex::scoped_lock lock1( b.mut );
        std::cerr << "Locking 1" << std::endl;

        // here the mutex is locked 1 times

        {
            boost::recursive_mutex::scoped_lock lock2( b.mut );
            std::cerr << "Locking 2" << std::endl;

            // here the mutex is locked 2 times

            std::cerr << "Auto UnLocking 2 ( by RAII) " << std::endl;
        }

        b.exemple();

        // here the mutex is locked 1 times

        std::cerr << "Auto UnLocking 1 ( by RAII) " << std::endl;
    }

    return 0;
}

注意const方法的互斥锁上的mutable。

boost mutex类型有一个scoped_lock typedef,这是一个很好的unique_lock类型。

答案 2 :(得分:2)

试试这个:

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

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

您使用相同的unique_lock _lock实例两次,这是一个问题。 您必须直接使用递归互斥锁的方法lock()和unock(),或使用两个不同的unique_lock实例,例如foe示例_lock_lock_2;。

<强>更新

我想补充一点,您的课程有公共方法lock()unlock(),从我的观点来看,这是一个不错的主意。在实际程序中将unique_lock作为类的成员也必须经常是一个坏主意。