Почему boost::recursive_mutex не работает должным образом?
У меня есть собственный класс, который использует бьют-мьютексы и блокировки вроде этого (только соответствующие части):
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 ответа
Попробуй это:
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, как например враг _lock
а также _lock_2
;.
Обновить
Я хотел бы добавить, что у вашего класса есть открытые методы lock()
а также unlock()
и с моей точки зрения в реальной программе это плохая идея. Кроме того, наличие уникального_класса в качестве члена класса в реальной программе часто является плохой идеей.
Как следует из названия, Mutex 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
позволяет блокировать цепочку в заданном потоке, что может произойти, если у вас есть сложные операции, которые в некоторых случаях вызывают друг друга.
Например, давайте добавим твик 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
это не получится.
Блокировка должна быть не частью защищенного ресурса, а вызывающей стороны, поскольку у вас есть один вызывающий поток. Они должны использовать разные 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.
А типы буст-мьютекса имеют определение типа scoped_lock, которое является хорошим типом unique_lock.